무한 목록에서 100 개의 가장 높은 숫자 얻기


53

내 친구 중 한 명이이 인터뷰 질문을 받았습니다.

"무한한 숫자 목록에서 나오는 숫자의 흐름이 일정하다. 그 중 어느 시점에서든 최고 100 개의 최고 숫자를 반환하기 위해 데이터 구조를 유지해야한다. 모든 숫자는 정수일 뿐이다."

이것은 간단합니다. 정렬 된 목록을 내림차순으로 유지하고 해당 목록에서 가장 낮은 번호의 트랙을 유지해야합니다. 얻은 새 숫자가 가장 낮은 숫자보다 큰 경우에는 가장 낮은 숫자를 제거하고 필요에 따라 정렬 된 목록에 새 숫자를 삽입해야합니다.

그런 다음 질문이 확장되었습니다-

"삽입 순서가 O (1)이어야합니까? 가능합니까?"

내가 아는 한 정렬 알고리즘을 사용하여 목록에 새 번호를 추가하고 다시 정렬하더라도 퀵 정렬을 위해 O (로그온)이 가장 좋습니다 (생각합니다). 그래서 친구는 불가능하다고 말했습니다. 그러나 그는 확신이 없었으며 목록이 아닌 다른 데이터 구조를 유지하도록 요청했습니다.

균형 잡힌 이진 트리를 생각했지만 거기에서도 1의 순서로 삽입 할 수는 없습니다. 그래서 같은 질문이 있습니다. 위의 문제에 대해 1의 순서로 삽입 할 수있는 데이터 구조가 있는지 또는 전혀 불가능한지 알고 싶었습니다.


19
어쩌면 이것은 단지 질문을 오해하는 것일 수도 있지만 왜 정렬 된 목록 을 유지해야 합니까? 왜 가장 낮은 숫자를 추적하고 그보다 높은 숫자가 발견되면 목록을 정렬하지 않고 가장 낮은 숫자를 제거하고 새 숫자를 입력하십시오. 그것은 당신에게 O (1)을 줄 것입니다.
EdoDodo

36
@ EdoDodo-그리고 그 작업 후에 새로운 가장 작은 숫자가 무엇인지 어떻게 알 수 있습니까?
Damien_The_Unbeliever 8

19
[O (100 * log (100)) = O (1)] 목록을 정렬하거나 최소 [O (100) = O (1)]에 대해 선형 검색을 수행하여 가장 작은 새 숫자를 얻습니다. 목록은 일정한 크기이므로 이러한 모든 작업은 일정한 시간입니다.
Random832

6
전체 목록을 정렬하지 않아도됩니다. 가장 높은 숫자 나 두 번째로 높은 숫자는 중요하지 않습니다. 가장 낮은 것이 무엇인지 알아야합니다. 따라서 새로운 숫자를 삽입 한 후에는 100 개의 숫자를 순회하면서 가장 낮은 숫자를 볼 수 있습니다. 그것은 일정한 시간입니다.
Tom Zych

27
연산 의 점근 적 순서 는 문제의 크기가 제한없이 커질 수 있는 경우 에만 흥미 롭습니다 . 어느 수량이 제한없이 성장하고 있는지는 확실하지 않습니다. 크기가 100으로 제한되는 문제에 대한 점근 적 순서가 무엇인지 묻는 것처럼 들립니다. 그것은 현명한 질문이 아닙니다. 제한없이 무언가가 성장해야합니다. 질문이 "O (1) 시간에 상위 100이 아닌 상위 n을 유지하기 위해 할 수 있습니까?" 그렇다면 질문은 합리적입니다.
Eric Lippert

답변:


35

k는 알고 자하는 가장 높은 숫자의 수라고 가정합니다 (예에서 100). 그런 다음에 새 번호를 추가 할 수 O(k)도있는가 O(1). 왜냐하면 O(k*g) = O(g) if k is not zero and constant.


6
O (50)은 O (1)이 아니라 O (n)입니다. O (1) 시간의 길이 N 목록에 삽입한다는 것은 시간이 N 값에 의존하지 않음을 의미합니다. 즉, 100이 10000이되면 50이 5000이되어서는 안됩니다.

18
@ hamstergene-그러나이 질문 N의 경우 정렬 된 목록의 크기 또는 지금까지 처리 된 항목 수입니까? 10000 개의 항목을 처리하고 목록에서 상위 100 개의 항목을 유지하거나 1000000000 개의 항목을 처리하고 정렬 된 목록에 상위 100 개의 항목을 유지하면 해당 목록의 삽입 비용은 동일하게 유지됩니다.
Damien_The_Unbeliever 8

6
@ hamstergene :이 경우 기본 사항이 잘못되었습니다. Wikipedia 링크에는 속성 ( "상수에 의한 곱셈")이 O(k*g) = O(g) if k not zero and constant있습니다. => O(50*1) = O(1).
duedl0r

9
duedl0r이 옳다고 생각합니다. 문제를 줄이고 최소값과 최대 값 만 필요하다고합시다. 최소값과 최대 값이 2이므로 O (n)입니까? (n = 2). 2 번은 문제 정의의 일부입니다. 상수이므로, O (something)와 동등한 O (k * something)의 ak입니다
xanatos

9
@ hamstergene : 무슨 기능에 대해 이야기하고 있습니까? 값 (100)은 .. 나에게 꽤 일정한 보인다
duedl0r

19

목록을 정렬하지 마십시오. 새 숫자를 삽입할지 여부를 알아내는 데 시간이 오래 걸리지 만 삽입 은 O (1)입니다.


7
나는 이것이 당신에게 스마트 앨범 상을 줄 것이라고 생각합니다 . * 8 ')
Mark Booth

4
@Emilio, 당신은 기술적으로 정확합니다 – 물론 그것은 가장 좋은 종류입니다…
Gareth

1
그러나 100 개 숫자 중 가장 낮은 숫자를 유지 한 다음 O (1)에 삽입해야하는지 여부를 결정할 수도 있습니다. 그런 다음 숫자를 삽입 할 때만 새 최저 숫자를 검색해야합니다. 그러나 이것은 삽입 할 것인지 아닌지를 결정하는 것보다 드문 일입니다.
Andrei Vajna II

12

이것은 쉬워요. 상수 목록의 크기, 따라서 목록의 정렬 시간은 일정합니다. 일정한 시간에 실행되는 작업을 O (1)라고합니다. 따라서 고정 크기 목록의 경우 목록 정렬은 O (1)입니다.


9

100 개의 숫자를 통과하면 다음 숫자에 발생할 수있는 최대 비용은 숫자가 가장 높은 100 개의 숫자인지 확인하는 비용 ( CheckTime 이라고 레이블을 붙입니다 )과 해당 세트에 입력하고 배출하는 비용입니다 가장 낮은 것 ( EnterTime 이라고 부름 ), 상수 시간 (적어도 경계 숫자의 경우) 또는 O (1) .

Worst = CheckTime + EnterTime

다음으로, 숫자 분포가 무작위이면 평균 비용은 더 많은 숫자를 줄입니다. 예를 들어, 101 번째 숫자를 최대 세트에 입력해야 할 확률은 100/101이고, 1000 번째 숫자는 1/10이고, n 번째 숫자는 100 / n입니다. 따라서 평균 비용에 대한 방정식은 다음과 같습니다.

Average = CheckTime + EnterTime / n

따라서 n 이 무한대에 가까워 지면 CheckTime 만 중요합니다.

Average = CheckTime

숫자가 바인딩되어 있으면 CheckTime 이 일정하므로 O (1) 시간입니다.

숫자가 제한되지 않으면 검사 시간이 더 많아 질수록 증가합니다. 이론적으로 이것은 최대 세트에서 가장 작은 숫자가 충분히 커지면 더 많은 비트를 고려해야하기 때문에 검사 ​​시간이 길어지기 때문입니다. 그것은 일정 시간보다 약간 더 높은 것처럼 보입니다. 그러나 n 이 무한대에 가까워 지면 다음 숫자가 가장 높은 세트에 있을 확률이 0에 가까워지고 더 많은 비트를 고려해야 할 가능성도 0에 가까워 질 것입니다. 이는 O (1)에 대한 인수입니다 시각.

나는 긍정적이지 않지만 내 직감은 그것이 O (log (log (n))) 시간 이라고 말합니다 . 숫자가 가장 적게 증가 할 확률은 로그이고 각 검사에 대해 고려해야 할 비트 수가 로그 일 수도 있기 때문입니다. 나는 확실하지 않기 때문에 다른 사람들 이이 일에 관심이 있습니다 ...


목록이 임의적이라는 것을 제외하고, 계속 증가하는 숫자의 목록이라면?
dan_waterworth

@dan_waterworth : 무한 목록이 임의적이며 방금 증가 할 확률이 높아지면 (1 / ∞ 일 가능성이 높습니다!), 이는 CheckTime + EnterTime각 수 에 대한 최악의 시나리오에 맞습니다 . 숫자를 억제 할 수 있습니다, 그래서 경우에만 의미가 CheckTime하고 EnterTime모두 증가 때문에 숫자의 크기 증가에 적어도 대수적 것이다.
Briguy37

1
숫자는 무작위가 아니며 임의적입니다. 확률에 대해 이야기하는 것은 의미가 없습니다.
dan_waterworth

@ dan_waterworth : 숫자가 임의적이라고 두 번 말했습니다. 어디서 구할 수 있습니까? 또한 임의의 경우부터 시작하여 임의의 숫자에 통계를 적용하고 중재자에 대해 더 많이 알수록 정확도를 향상시킬 수 있다고 생각합니다. 예를 들어, 중재자 인 경우 중재인 인 경우보다 계속 증가하는 숫자를 선택할 가능성이 더 높습니다.)
Briguy37

7

Binary Heap Trees 를 알고 있다면 이것은 쉽다 . 이진 힙은 평균 상수 시간 O (1)에서의 삽입을 지원합니다. 첫 x 요소에 쉽게 액세스 할 수 있습니다.


왜 필요하지 않은 요소를 저장합니까? (너무 낮은 값) 사용자 정의 알고리즘이 더 적합합니다. 가장 낮은 값보다 높지 않으면 값을 '추가 할 수 없습니다'라고 말하지 않습니다.
Steven Jeuris

모르겠다, 내 직감은 (일부 맛의) 힙이 이것을 꽤 잘 끌 수 있다고 나에게 말한다. 그렇다고 모든 요소를 ​​유지해야한다는 의미는 아닙니다. 나는 그것을 연구하지는 않았지만 "느낌"(TM)입니다.
Rig

3
힙은 일부 m 레벨 아래의 항목을 버리도록 수정할 수 있습니다 (이진 힙 및 k = 100의 경우 노드 수 = 2 ^ m-1이므로 m은 7 임). 이것은 속도를 늦출 것이지만 여전히 일정한 시간으로 상각됩니다.
Plutor

3
이진 최소 힙을 사용한 경우 (상단이 항상 확인하는 최소값이기 때문에) 맨 위에 새 숫자가 있으면 새 요소를 삽입하기 전에 맨 위 요소를 제거해야합니다. . 최상위 (최소) 요소를 제거하면 트리의 모든 수준을 한 번 통과해야하므로 O (logN)가됩니다. 따라서 인서트가 평균 O (1)라는 것은 기술적으로 사실입니다. 실제로 숫자> 분을 찾을 때마다 여전히 O (logN)이기 때문입니다.
Scott Whitlock

1
@Plutor, 바이너리 힙이 제공하지 않는다고 보장한다고 가정합니다. 이진 트리로 시각화하면 왼쪽 분기의 각 요소가 오른쪽 분기의 모든 요소보다 작을 수 있지만 가장 작은 요소가 루트에 가장 가깝다고 가정합니다.
피터 테일러

6

질문에 의해 면접관이 실제로“각 수신 번호가 일정한 시간에 처리되도록 할 수 있습니까?”라고 물었다면 이미 많은 사람들이 지적한대로 (예 : @ duedl0r의 답변 참조) 친구의 해결책은 이미 O (1)이며, 그가 정렬되지 않은 목록을 사용했거나 거품 정렬을 사용했거나 다른 것을 사용하더라도 그렇게 될 것입니다. 이 경우 까다로운 질문이 아니거나 잘못 기억하지 않는 한 질문은 의미가 없습니다.

나는 면접관의 질문이 의미가 있다고 생각하는데, 그는 무언가를 O (1)로 만드는 방법을 묻지 않았으며, 그것은 이미 매우 명백합니다.

입력 알고리즘의 크기가 무한정 커질 때에 만 질문 알고리즘의 복잡성이 의미가 있으며 여기서 커질 수있는 유일한 입력은 100입니다. 나는 실제 질문이 "친구의 솔루션에서와 같이 O (N)이 아닌 숫자 당 O (1) 시간을 소비 할 수 있습니까?"라고 가정했습니다.

가장 먼저 염두에 두어야 할 것은 계수 정렬입니다 .O (m) 공간 사용 가격에 대한 Top-N 문제의 숫자 당 O (1) 시간의 복잡성을 구입할 것입니다. 여기서 m 은 들어오는 숫자의 길이입니다. . 네, 가능합니다.


4

삽입 시간이 일정한 Fibonacci heap으로 구현 된 최소 우선 순위 큐를 사용하십시오 .

1. Insert first 100 elements into PQ
2. loop forever
       n = getNextNumber();
       if n > PQ.findMin() then
           PQ.deleteMin()
           PQ.insert(n)

4
"작업이 삭제에서 최소 작업 삭제 O(log n)상각 시간" , 그래서 이것은 여전히 초래 O(log k)k저장소에 항목의 양입니다.
Steven Jeuris

1
이것은 삭제 분이 O (log n) 에서 작동하기 때문에 (위키 백과에 따라 ) "smart-aleck award"라는 별명을 가진 Emilio의 대답 과 다르지 않습니다 .
Nicole

@Renesis 에밀리오의 대답은 O (k)는 최소를 찾는 것, 광산은 O (K 로그)
게이브 Moothart

1
@Gabe Fair 충분히, 나는 원칙적으로 의미합니다. 다시 말해, 100을 일정하게 유지하지 않으면이 대답은 일정한 시간이 아닙니다.
Nicole

@Renesis 답변에서 (잘못된) 진술을 제거했습니다.
Gabe Moothart

2

이 작업은 필요한 숫자 목록의 길이 N에서 O (1) 인 알고리즘을 분명히 찾는 것입니다. 따라서 상위 100 개 숫자 또는 10000 개 숫자가 필요한 경우 삽입 시간은 O (1)이어야합니다.

여기서 요점은 목록 삽입에 O (1) 요구 사항이 언급되었지만 질문은 정수 공간에서 검색 시간 순서에 대해 아무 말도하지 않았지만 O (1)로 만들 수 있음이 밝혀졌습니다 게다가. 해결책은 다음과 같습니다.

  1. 키의 숫자와 값의 연결된 목록 포인터 쌍으로 해시 테이블을 배열하십시오. 각 포인터 쌍은 연결된 목록 시퀀스의 시작과 끝입니다. 이것은 일반적으로 다음 요소 중 하나 일 것입니다. 링크 된 목록의 모든 요소는 다음으로 높은 숫자를 가진 요소 옆에 있습니다. 따라서 링크 된 목록에는 정렬 된 필수 숫자 순서가 포함됩니다. 가장 낮은 숫자의 레코드를 유지하십시오.

  2. 랜덤 스트림에서 새로운 숫자 x를 가져옵니다.

  3. 마지막으로 기록 된 가장 낮은 숫자보다 높습니까? 예 => 4 단계, 아니오 => 2 단계

  4. 방금 가져온 숫자로 해시 테이블을 누르십시오. 출품작이 있습니까? 예 => 5 단계. 아니오 => 새로운 숫자 x-1을 가져 와서이 단계를 반복하십시오 (이것은 간단한 하향 선형 검색입니다. 여기 나와 함께하시면됩니다. 개선 될 수 있으며 방법을 설명하겠습니다)

  5. 해시 테이블에서 가져온 목록 요소를 사용하여 링크 된 목록의 요소 바로 뒤에 새 숫자를 삽입하고 해시를 업데이트하십시오.

  6. 기록 된 가장 낮은 숫자 l을 가져 와서 해시 / 목록에서 제거하십시오.

  7. 방금 가져온 숫자로 해시 테이블을 누르십시오. 출품작이 있습니까? 예 => 8 단계. 아니오 => 새로운 숫자 l + 1을 취하고이 단계를 반복하십시오 (이것은 간단한 상향 선형 검색입니다).

  8. 긍정적 인 숫자로 숫자는 새로운 가장 낮은 숫자가됩니다. 2 단계로 이동

중복 값을 허용하려면 해시는 실제로 중복 된 요소의 링크 된 목록 순서의 시작과 끝을 유지해야합니다. 주어진 키에서 요소를 추가하거나 제거하면 지정된 범위가 증가하거나 감소합니다.

여기에 삽입은 O (1)입니다. 언급 된 검색은 O (숫자 사이의 평균 차이)와 같은 것 같습니다. 평균 차이는 숫자 공간의 크기에 따라 증가하지만 필요한 숫자 목록의 길이에 따라 감소합니다.

따라서 숫자 공간이 큰 경우 (예 : 4 바이트 int 유형, 0에서 2 ^ 32-1까지) N = 100 인 경우 선형 검색 전략은 매우 좋지 않습니다. 이 성능 문제를 해결하기 위해 적절한 키를 만들기 위해 숫자가 더 큰 크기 (예 : 1, 10, 100, 1000)로 반올림되는 병렬 해시 테이블 세트를 유지할 수 있습니다. 이런 식으로 기어를 위아래로 움직여 필요한 검색을 더 빨리 수행 할 수 있습니다. 그런 다음 성능은 O (log numberrange)가된다.

이것을 더 명확하게하기 위해, 197이라는 숫자가 있다고 상상해보십시오. '190'으로 10s 해시 테이블을 치면 가장 가까운 10으로 반올림됩니다. 아무것도? 아니요. 120이 될 때까지 10 초 안에 내려갑니다. 그러면 1s 해시 테이블에서 129에서 시작한 다음 무언가를 칠 때까지 128, 127을 시도 할 수 있습니다. 연결 목록에서 197을 삽입 할 위치를 찾았습니다. 197 해시 테이블을 197 항목으로, 10s 해시 테이블을 190으로, 100s로 100을 100으로 설정해야합니다. 숫자 범위 로그의 10 배입니다.

나는 세부 사항 중 일부가 잘못되었을 수도 있지만 이것이 프로그래머 교환이고 컨텍스트가 인터뷰이기 때문에 위의 상황이 그 상황에 대해 설득력있는 대답이기를 바랍니다.

편집 병렬 해시 테이블 체계를 설명하기 위해 여기에 약간의 세부 사항을 추가했으며 언급 한 가난한 선형 검색이 O (1) 검색으로 대체 될 수 있음을 의미합니다. 또한 가장 낮은 숫자로 해시 테이블을보고 다음 요소로 진행하여 바로 다음 단계를 검색 할 수 있기 때문에 다음으로 가장 낮은 숫자를 검색 할 필요가 없다는 것을 깨달았습니다.


1
검색은 삽입 기능의 일부 여야합니다. 독립 기능은 아닙니다. 검색은 O (n)이므로 삽입 함수도 O (n)입니다.
Kirk Broadhurst

아니요. 더 많은 해시 테이블을 사용하여 숫자 공간을 더 빨리 통과하는 데 사용한 전략을 사용하면 O (1)입니다. 답을 다시 읽으십시오.
베네딕트

1
@ 베네딕트, 귀하의 답변은 4 단계와 7 단계에서 선형 검색이 있음을 분명히 말하고 있습니다. 선형 검색은 O (1)이 아닙니다.
피터 테일러

예, 그렇습니다. 그러나 나중에 다루겠습니다. 실제로 나머지를 읽으시겠습니까? 필요한 경우 답변을 수정하여 명확하게 표시합니다.
베네딕트

@ 베네딕트 당신이 맞습니다-검색을 제외하고, 당신의 대답은 O (1)입니다. 불행히도이 솔루션은 검색 없이는 작동하지 않습니다.
커크 브로드 허스트

1

숫자가 Integer와 같은 고정 데이터 유형이라고 가정 할 수 있습니까? 그렇다면 추가되는 모든 단일 숫자의 집계를 유지하십시오. 이것은 O (1) 작업입니다.

  1. 가능한 많은 요소가있는 배열을 선언하십시오.
  2. 스트리밍 될 때 각 번호를 읽습니다.
  3. 숫자를 집계하십시오. 당신이 그것을 절대로 필요로하지 않기 때문에 그 숫자가 100 번 이미 계산 되었다면 그것을 무시하십시오. 이를 통해 오버플로가 무한히 커지는 것을 방지 할 수 있습니다.
  4. 2 단계부터 반복하십시오.

VB.Net 코드 :

Const Capacity As Integer = 100

Dim Tally(Integer.MaxValue) As Integer ' Assume all elements = 0
Do
    Value = ReadValue()
    If Tally(Value) < Capacity Then Tally(Value) += 1
Loop

목록을 반환하면 원하는 시간이 소요될 수 있습니다. 목록의 끝에서 간단히 종료하고 기록 된 최고 100 개의 값으로 새 목록을 작성하십시오. 이것은 O (n) 연산이지만, 그것은 무의미합니다.

Dim List(Capacity) As Integer
Dim ListCount As Integer = 0
Dim Value As Integer = Tally.Length - 1
Dim ValueCount As Integer = 0
Do Until ListCount = List.Length OrElse Value < 0
    If Tally(Value) > ValueCount Then
        List(ListCount) = Value
        ValueCount += 1
        ListCount += 1
    Else
        Value -= 1
        ValueCount = 0
    End If
Loop
Return List

편집 : 실제로 고정 데이터 유형인지는 중요하지 않습니다. 메모리 (또는 하드 디스크) 소비에 대한 제한이없는 경우 양의 정수 범위에서이 작업을 수행 할 수 있습니다.


1

백개의 숫자는 크기가 100 인 배열에 쉽게 저장됩니다. 현재 작업이 주어지면 모든 트리, 목록 또는 세트가 과도합니다.

들어오는 숫자가 배열에서 가장 낮은 값 (= 마지막)보다 높으면 모든 항목을 실행합니다. 새로운 숫자보다 작은 첫 번째 숫자를 찾으면 (멋진 검색을 사용하여), 나머지 배열을 실행하여 각 항목을 "아래로"밀어냅니다.

목록을 처음부터 정렬 한 상태로 유지하므로 정렬 알고리즘을 전혀 실행할 필요가 없습니다. 이것은 O (1)입니다.


0

이진 최대 힙을 사용할 수 있습니다. 최소 노드 (알 수 없거나 널일 수 있음)에 대한 포인터를 추적해야합니다.

처음 100 개의 숫자를 힙에 삽입하여 시작합니다. 최대 값이 맨 위에 있습니다. 이 작업이 완료되면 항상 100 개의 숫자를 유지합니다.

그런 다음 새로운 번호를 받으면 :

if(minimumNode == null)
{
    minimumNode = findMinimumNode();
}
if(newNumber > minimumNode.Value)
{
    heap.Remove(minimumNode);
    minimumNode = null;
    heap.Insert(newNumber);
}

불행히도 findMinimumNodeO (n)이며 인서트 당 한 번만 비용이 발생합니다 (그러나 인서트 중은 아님). 최소 노드를 제거하고 새 노드를 삽입하는 것은 평균적으로 힙의 맨 아래로 향하기 때문에 O (1)입니다.

Binary Min-Heap을 사용하여 다른 방법으로 갈 때, min은 맨 위에 있으며, 비교할 min을 찾는 데는 좋지만 min을> min 인 새 숫자로 바꿔야 할 때 짜증이납니다. 최소 노드를 제거하고 (항상 O (logN)) 새 노드를 삽입해야합니다 (평균 O (1)). 따라서 여전히 Max-Heap보다 우수하지만 O (1)이 아닌 O (logN)이 있습니다.

물론 N이 일정하면 항상 O (1)이됩니다. :)

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.