10 억 개의 숫자 중에서 100 개의 가장 큰 숫자를 찾는 프로그램을 작성하십시오


300

최근에 한 인터뷰에서 "10 억 개의 숫자 중 100 개의 가장 큰 숫자를 찾는 프로그램을 작성하십시오"라는 질문을 받았습니다.

나는 O (nlogn) 시간 복잡성으로 배열을 정렬하고 마지막 100 숫자를 취하는 무차별 대입 솔루션 만 제공 할 수있었습니다.

Arrays.sort(array);

면접관은 더 나은 시간 복잡성을 찾고 있었지만 몇 가지 다른 솔루션을 시도했지만 그에 대답하지 못했습니다. 더 나은 시간 복잡성 솔루션이 있습니까?


70
어쩌면 문제는 정렬 질문이 아니라 추구 하는 질문이라는 것입니다.
geomagas

11
기술적 인 메모로서, 정렬이 문제를 해결하는 가장 좋은 방법은 아니지만, 그것이 그것이 무차별 적이라고 생각하지는 않습니다-나는 그것을하는 더 나쁜 방법을 생각할 수 있습니다.
Bernhard Barker

88
방금 더 어리석은 무차별 대입 방법을 생각했습니다 ... 10 억 요소 배열에서 100 가지 요소의 가능한 모든 조합을 찾고이 조합 중 가장 큰 합계를 갖는 것을보십시오.
Shashank

10
참고 모든 결정 (정정) 알고리즘이 O(1)더 크기 증가가 없기 때문에,이 경우. 면접관은 "n >> m으로 n의 배열에서 m 개의 가장 큰 요소를 찾는 방법"을 물어야했습니다.
Bakuriu

답변:


328

100 개의 가장 큰 숫자의 우선 순위 큐를 유지하고 큐에서 가장 작은 숫자 (큐의 헤드)보다 큰 숫자가 발생할 때마다 큐의 헤드를 제거하고 새 번호를 추가 할 때마다 10 억 개의 숫자를 반복 할 수 있습니다. 대기열에.

편집하다: Dev가 지적했듯이 우선 순위 대기열이 힙으로 구현되면 대기열에 삽입하는 것이 복잡합니다.O(logN)

최악의 경우 billionlog2(100) 보다 더 나은 것을 습니다.billionlog2(billion)

일반적으로 N 숫자 집합에서 가장 큰 K 숫자가 필요한 경우 복잡도는 O(NlogK) 보다 큽니다 O(NlogN). 이는 K가 N에 비해 매우 작을 때 매우 중요합니다.

EDIT2 :

이 알고리즘의 예상 시간은 각 반복마다 삽입이 발생하거나 발생하지 않을 수 있으므로 매우 흥미 롭습니다. i 번째 숫자가 큐에 삽입 될 확률은 랜덤 변수가 i-K동일한 분포의 랜덤 변수 보다 클 확률입니다 (처음 k 개의 숫자가 큐에 자동으로 추가됨). 주문 통계 ( link 참조 )를 사용하여이 확률을 계산할 수 있습니다 . 예를 들어, 숫자가에서 임의로 균일하게 선택되었다고 가정하고 {0, 1}(iK 중) i 번째 숫자의 예상 값은 i (i-k)/i이고 임의 변수가이 값보다 클 확률은 다음과 같습니다.1-[(i-k)/i] = k/i 있습니다.

따라서 예상되는 삽입 수는 다음과 같습니다.

여기에 이미지 설명을 입력하십시오

예상 실행 시간은 다음과 같이 표현할 수 있습니다.

여기에 이미지 설명을 입력하십시오

( k첫 번째 k요소로 대기열을 생성 한 다음 n-k비교 및 위에서 설명한 예상 삽입 횟수는 각각 평균을 취합니다.log(k)/2 시간 )

N비해 매우 큰 K경우이 표현은보다 훨씬 더 가깝 n습니다 NlogK. 이것은 질문의 경우와 같이 10 억 회에 비해 매우 작은 반복 후에도 숫자가 대기열에 삽입 될 가능성이 매우 적다는 점에서 다소 직관적입니다.


6
실제로 는 각 인서트에 대해서만 O (100) 입니다.
MrSmith42

8
@RonTeller 연결된 목록을 효율적으로 이진 검색 할 수 없으므로 우선 순위 대기열이 일반적으로 힙으로 구현됩니다. 설명 된 삽입 시간은 O (logn)이 아닌 O (n)입니다. Skizz가 두 번째 추측을 할 때까지 처음으로 (순서대로 대기열 또는 우선 순위 대기열) 제대로했습니다.
Dev

17
@ThomasJungblut billion도 상수이므로,이 경우 O (1)입니다 : P
Ron Teller

9
@RonTeller : 질문에 대한 우려의 보통 이런 종류의 Google 검색 결과 수십억에서 상위 10 페이지 또는 워드 클라우드 (50 개) 가장 자주 단어, 또는 MTV에 10 개 가장 인기있는 노래 등 그래서를 찾는처럼 생각 나는에, 믿고 정상적인 상황 에 비해 k 일정 하고 작은 것을 고려하는 것이 안전 합니다 n. 그러나이 "정상적인 상황"을 항상 명심해야합니다.
ffriend

5
1G 품목이 있기 때문에 1000 개의 요소를 무작위로 샘플링하고 가장 큰 100 개를 선택하십시오. 이는 변형 사례 (정렬, 역 정렬, 대부분 정렬)를 피하여 인서트 수를 상당히 줄여야합니다.
ChuckCottrill

136

인터뷰에서 이것이 요청되면 인터뷰어는 아마도 알고리즘에 대한 지식뿐만 아니라 문제 해결 과정을보고 싶을 것입니다.

설명은 매우 일반적이므로 문제를 명확하게하기 위해이 숫자의 범위 나 의미를 물어볼 수 있습니다. 이렇게하면 면접관에게 깊은 인상을 줄 수 있습니다. 예를 들어이 숫자가 특정 국가 (예 : 중국)의 연령을 나타내는 경우 훨씬 쉬운 문제입니다. 살아있는 사람이 200보다 늙지 않다는 합리적인 가정하에, 200 (아마도 201) 크기의 int 배열을 사용하여 한 번의 반복으로 같은 나이를 가진 사람들의 수를 계산할 수 있습니다. 여기서 색인은 나이를 의미합니다. 이 후 100 가장 큰 숫자를 찾는 케이크 조각입니다. 그런데이 알고를 카운팅 정렬 이라고 합니다 .

어쨌든, 질문을보다 구체적이고 명확하게하는 것은 인터뷰에서 당신에게 좋습니다.


26
아주 좋은 지적입니다. 아무도 그 숫자의 분포에 대해 묻거나 지시하지 않았습니다. 문제에 접근하는 방법에 모든 차이를 만들 수 있습니다.
NealB

13
이 답변을 확장하기에 충분합니다. 분포를 가정 할 수 있도록 최소 / 최대 값을 얻으려면 숫자를 한 번 읽습니다. 그런 다음 두 가지 옵션 중 하나를 수행하십시오. 범위가 충분히 작 으면 숫자가 발생할 때 간단히 확인할 수있는 배열을 만듭니다. 범위가 너무 큰 경우 위에서 논의한 정렬 된 힙 알고리즘을 사용하십시오.
Richard_G

2
나는 면접관에게 질문을하는 것이 실제로 많은 차이를 만든다는 데 동의한다. 실제로 컴퓨팅 성능에 제한이 있거나 없는지에 대한 질문은 여러 컴퓨팅 노드를 사용하여 솔루션을 병렬화하는 데 도움이 될 수도 있습니다.
Sumit Nigam

1
@R_G 전체 목록을 살펴볼 필요가 없습니다. 유용한 통계를 얻기 위해 목록의 작은 부분 (예 : 백만)의 임의 구성원을 샘플링하기에 충분합니다.
Itamar

해당 솔루션에 대해 생각하지 않은 사람들을 위해 계산 정렬 en.wikipedia.org/wiki/Counting_sort에 대해 읽어 보는 것이 좋습니다 . 그것은 실제로 매우 일반적인 인터뷰 질문입니다 : 배열을 O (nlogn)보다 더 잘 정렬 할 수 있습니까? 이 질문은 확장 된 것입니다.
막심 체 라미

69

O (n)이 걸리는 숫자를 반복 할 수 있습니다

현재 최소값보다 큰 값을 찾을 때마다 크기가 100 인 순환 큐에 새 값을 추가하십시오.

해당 순환 대기열의 최소값은 새로운 비교 값입니다. 그 대기열에 계속 추가하십시오. 가득 찬 경우 큐에서 최소값을 추출하십시오.


3
작동하지 않습니다. 예를 들어 {1, 100, 2, 99} 중 상위 2 개를 찾으면 {100,1}이 상위 2
개가

7
정렬 된 대기열을 유지할 수 없습니다. (다음 작은 요소를 위해 매번 홀 대기열을 검색하지 않으려는 경우)
MrSmith42

3
@ MrSmith42 힙에서와 같이 부분 정렬이면 충분합니다. Ron Teller의 답변을 참조하십시오.
Christopher Creutzig

1
예, 추출 최소 큐가 힙으로 구현되었다고 조용히 가정했습니다.
Regenschein

크기가 100 인 순환 큐를 사용하는 최소 힙은 맨 위에 최소 백 개의 숫자를 사용합니다. 큐의 경우 o (n)에 비해 삽입에 O (log n) 만 사용됩니다
techExplorer

33

나는 이것이 '알고리즘'으로 태그되어 있다는 것을 깨달았지만 아마도 '인터뷰'로 태그되어야하기 때문에 다른 옵션을 던져 버릴 것입니다.

10 억 숫자의 출처는 무엇입니까? 데이터베이스 인 경우 '값 desc 한도 100으로 테이블 순서에서 값 선택'을 수행하면 작업이 매우 훌륭하게 수행됩니다. 사투리 차이가있을 수 있습니다.

이것은 일회성입니까, 아니면 반복 될 것입니까? 반복한다면 얼마나 자주? 일회성이고 데이터가 파일에 있으면 'cat srcfile | 정렬 (필요에 따라 옵션) | head -100 '은 컴퓨터가이 사소한 일을 처리하는 동안 지불해야하는 생산적인 작업을 신속하게 수행하게합니다.

반복되는 경우, 상위 100 개를 지속적으로보고 할 수 있도록 초기 답변을 얻고 결과를 저장 / 캐시하는 적절한 접근 방식을 선택하는 것이 좋습니다.

마지막으로이 고려 사항이 있습니다. 초급 직업을 찾고 괴짜 관리자 또는 미래의 동료와 인터뷰하고 있습니까? 그렇다면 상대 기술 장단점을 설명하는 모든 방법을 사용할 수 있습니다. 좀 더 관리적인 직업을 찾고 있다면, 솔루션의 개발 및 유지 보수 비용에 관심이있는 관리자처럼 접근하여 "정말 감사합니다"라고 말하고 면접관이 CS 퀴즈에 중점을두고 싶다면 떠나십시오. . 그와 당신은 거기에 많은 발전 가능성이 없을 것입니다.

다음 인터뷰에서 더 나은 행운을 빕니다.


2
탁월한 답변. 다른 사람들은이 질문의 기술적 측면에 집중 한 반면이 응답은 비즈니스의 사회적 부분을 다루고 있습니다.
vbocan

2
나는 당신이 당신에게 감사하다고 말하고 인터뷰를 남기고 끝날 때까지 기다리지 않을 것이라고 상상하지 못했습니다. 내 마음을 열어 주셔서 감사합니다.
UrsulRosu

1
왜 10 억 개의 요소를 생성하고 100 개의 가장 큰 요소를 추출 할 수 없습니까? 이 방법으로 비용 = O (billion) + 100 * O (log (billion)) ??
Mohit Shah

17

이에 대한 나의 즉각적인 반응은 힙을 사용하는 것이지만 한 번에 모든 입력 값을 유지하지 않고 QuickSelect를 사용하는 방법이 있습니다.

200 크기의 배열을 만들고 처음 200 개의 입력 값으로 채 웁니다. QuickSelect를 실행하고 낮은 100을 버리고 100 개의 빈 공간을 남겨 둡니다. 다음 100 개의 입력 값을 읽고 QuickSelect를 다시 실행하십시오. 전체 입력을 100 개씩 배치 할 때까지 계속하십시오.

마지막에는 상위 100 개의 값이 있습니다. N 값의 경우, 대략 N / 100 배 정도 빠른 선택을 실행했습니다. 각 Quickselect는 상수의 약 200 배이므로 총 비용은 상수의 2N 배입니다. 이것은이 설명에서 100으로 배선하려는 매개 변수 크기에 관계없이 입력 크기가 선형으로 보입니다.


10
작지만 중요한 최적화를 추가 할 수 있습니다. QuickSelect를 실행하여 크기 200 배열을 분할 한 후 상위 100 개 요소 중 최소값이 알려져 있습니다. 그런 다음 전체 데이터 세트를 반복 할 때 현재 값이 현재 최소값보다 큰 경우에는 100 개 미만의 값만 채 웁니다. C ++ 에서이 알고리즘의 간단한 구현은 libstdc ++ partial_sort가 2 억 32 비트의 데이터 세트 int(MT19937을 통해 생성되고 균일하게 분포 됨) 에서 직접 실행 되는 것과 같습니다.
dyp

1
좋은 생각-최악의 경우 분석에는 영향을 미치지 않지만 가치가있는 것처럼 보입니다.
mcdowella

@mcdowella 한번 시도해 볼 가치가 있습니다. 감사합니다!
userx

8
이것이 바로 구아바 가하는 일 Ordering.greatestOf(Iterable, int)입니다. 절대적으로 선형 시간과 단일 패스이며 매우 귀여운 알고리즘입니다. FWIW에는 실제 벤치 마크도 있습니다. 상수 요인은 평균적인 경우 우선 순위 대기열보다 머리가 느리지 만이 구현은 "최악의 경우"입력 (예 : 오름차순 입력)에 훨씬 더 강합니다.
Louis Wasserman

15

빠른 선택 알고리즘 을 사용 하여 (순서별) 인덱스 [billion-101]에서 숫자를 찾은 다음 숫자를 반복하여 해당 숫자보다 큰 숫자를 찾을 수 있습니다.

array={...the billion numbers...} 
result[100];

pivot=QuickSelect(array,billion-101);//O(N)

for(i=0;i<billion;i++)//O(N)
   if(array[i]>=pivot)
      result.add(array[i]);

이 알고리즘 시간은 다음과 같습니다. 2 XO (N) = O (N) (평균 사례 성능)

Thomas Jungblut 와 같은 두 번째 옵션 은 다음과 같습니다.

사용 MAX 힙을 빌드하면 O (N)가 걸리고, 상위 100 개의 최대 숫자는 힙 맨 위에 오게됩니다. 힙 (100 XO (Log (N)))에서 힙을 가져 오면됩니다.

이 알고리즘 시간은 다음과 같습니다 .O (N) + 100 XO (Log (N)) = O (N)


8
전체 목록을 세 번 작업하고 있습니다. 바이오 1 개. 정수는 대략 4GB입니다. 메모리에 맞지 않으면 어떻게 하시겠습니까? 이 경우 quickselect가 최악의 선택입니다. 한 번만 반복하고 상위 100 개 항목의 힙을 유지하는 것은 IMHO가 O (n)에서 가장 성능이 우수한 솔루션입니다 (힙에서 n이 100 = 일정 = 매우 작으므로 힙 삽입의 O (log n)을 차단할 수 있음에 유의하십시오) ).
Thomas Jungblut

3
여전히 그렇더라도 O(N)두 개의 빠른 선택과 다른 선형 스캔을 수행하는 것이 필요한 것보다 더 많은 오버 헤드입니다.
Kevin

이것은 PSEUDO 코드입니다 여기에 모든 솔루션이 더 많은 시간이 걸립니다 (O (NLOG (N) 또는 100 * O (N))
One Man Crew

1
100*O(N)(유효한 구문 인 경우) = O(100*N)= O(N)(100은 가변적 일 수 있습니다. 그렇다면 엄격하지 않습니다). 아, 그리고 Quickselect의 최악의 성능은 O (N ^ 2) (ouch)입니다. 메모리에 맞지 않으면 디스크에서 데이터를 두 번 다시로드하게되는데 이는 한 번보다 훨씬 나쁩니다 (병목 현상).
Bernhard Barker

이것이 최악의 경우는 아니지만 예상되는 피벗 선택 전략을 사용하여 예상되는 문제가 있습니다 (예 : 임의로 21 요소를 선택하고 피벗으로 21 요소의 중간 값을 선택). 비교 횟수는 다음과 같습니다. 임의로 작은 상수 c에 대해 최대 (2 + c) n의 높은 확률로 보장 c.
One Man Crew

10

다른 quickselect 솔루션이 다운 보트되었지만 quickselect가 크기가 100 인 큐를 사용하는 것보다 솔루션을 더 빨리 찾게된다는 사실은 여전히 ​​남아 있습니다. Quickselect의 예상 실행 시간은 2n + o (n)입니다. 아주 간단한 구현은

array = input array of length n
r = Quickselect(array,n-100)
result = array of length 100
for(i = 1 to n)
  if(array[i]>r)
     add array[i] to result

평균 3n + o (n) 비교가 필요합니다. 또한, quickselect가 배열에서 가장 큰 100 개의 항목을 가장 오른쪽에있는 100 개의 위치에 남겨둔다는 사실을 사용하여보다 효율적으로 만들 수 있습니다. 실제로, 실행 시간은 2n + o (n)으로 향상 될 수 있습니다.

이것이 최악의 경우는 아니지만 예상되는 피벗 선택 전략을 사용하여 예상되는 문제가 있습니다 (예 : 임의로 21 요소를 선택하고 피벗으로 21 요소의 중간 값을 선택). 비교 횟수는 다음과 같습니다. 임의로 작은 상수 c에 대해 최대 (2 + c) n의 높은 확률로 보장 c.

실제로 최적화 된 샘플링 전략 (예 : 임의로 샘플 sqrt (n) 요소를 선택하고 99 번째 백분위 수를 선택)을 사용하면 임의로 작은 c에 대해 실행 시간을 (1 + c) n + o (n)으로 줄일 수 있습니다. (K라고 가정하면, 선택 될 요소의 수는 o (n)입니다).

반면, 크기가 100 인 큐를 사용하려면 O (log (100) n) 비교가 필요하며 100의 로그베이스 2는 대략 6.6입니다.

크기 N의 배열에서 K가 가장 큰 K 요소를 선택하는보다 추상적 인 의미 에서이 문제를 생각하면 K = o (N)이지만 K와 N 모두 무한대로 이동하면 빠른 선택 버전의 실행 시간은 다음과 같습니다. O (N) 및 큐 버전은 O (N log K)가되므로 이런 의미에서 quickselect도 무조건 우수합니다.

의견에 따르면 대기열 솔루션은 임의의 입력에서 예상 시간 N + K log N에서 실행됩니다. 물론, 무작위 입력 가정은 질문에서 명시 적으로 언급하지 않는 한 절대 유효하지 않습니다. 대기열 솔루션은 임의의 순서로 배열을 순회하도록 만들 수 있지만, 이로 인해 전체 입력 배열을 치환하거나 길이가 N 인 새로운 배열을 할당 할뿐만 아니라 난수 생성기에 대한 N 호출의 추가 비용이 발생합니다. 무작위 지수.

문제로 인해 원래 배열의 요소를 이동할 수없고 메모리 할당 비용이 높아서 배열 복제가 옵션이 아닌 경우 이는 다른 문제입니다. 그러나 실행 시간 측면에서 이것은 최상의 솔루션입니다.


4
마지막 단락은 핵심 포인트입니다. 십억 개의 숫자로 모든 데이터를 메모리에 보관하거나 요소를 교환하는 것은 불가능합니다. (적어도 인터뷰 문제인 경우 문제를 해석하는 방법입니다.)
Ted Hopp

14
알고리즘 질문에서 데이터를 읽는 것이 문제인 경우 질문에서 언급해야합니다. "메모리에 맞지 않고 알고리즘 분석의 표준 인 폰 뉴먼 (von neuman) 모델에 따라 조작 할 수없는 디스크의 어레이를 제공했다"는 질문은 "어레이를 제공했습니다"라고 말합니다. 요즘에는 8 기가의 램이 장착 된 노트북을 구입할 수 있습니다. 10 억 개의 숫자를 기억한다는 아이디어가 어디에서 실현 될 수 없는지 잘 모르겠습니다. 현재 내 워크 스테이션에는 수십억 개의 메모리가 있습니다.
mrip

참고로 quickselect의 최악의 런타임은 O (n ^ 2)입니다 ( en.wikipedia.org/wiki/Quickselect 참조). )이며 입력 배열의 요소 순서도 수정합니다. 매우 큰 상수 ( en.wikipedia.org/wiki/Median_of_medians )를 가진 최악의 O (n) 솔루션을 가질 수 있습니다 .
pts

quickselect의 최악의 경우는 기하 급수적으로 일어날 가능성이 낮으므로 실제적인 목적으로는 이것이 중요하지 않습니다. 확률이 높을수록 임의의 작은 c에 대한 비교 수가 (2 + c) n + o (n)이되도록 quickselect를 수정하는 것은 쉽습니다.
mrip

"빠른 선택은 크기가 100 인 대기열을 사용하는 것보다 솔루션을 더 빨리 찾을 수 있다는 사실이 남아 있습니다." 힙 솔루션은 약 N + Klog (N) 비교 대 빠른 선택의 경우 2N 평균, 중간 값의 중앙값 2.95를 취합니다. 주어진 K에 대해 분명히 빠릅니다.
Neil G

5

10 억의 처음 100 개 숫자를 가져 와서 정렬하십시오. 이제 소스 번호가 100보다 작은 경우 수십억을 반복하여 정렬 순서로 삽입하십시오. 당신이 끝내는 것은 세트의 크기에 대해 O (n)에 훨씬 가까운 것입니다.


3
죄송하지만 내 것보다 더 자세한 답변을 보지 못했습니다.
Samuel Thurston

처음 500 개 정도의 숫자를 가져 와서 목록이 채워질 때만 정렬을 중단합니다 (낮은 400을 버림). (그리고 새로운 숫자가 선택된 100에서 가장 낮은 경우에만 목록에 추가한다는 것은 말할 나위도 없습니다.)
Hot Licks

4

두 가지 옵션 :

(1) 힙 (priorityQueue)

크기가 100 인 최소 힙을 유지하십시오. 어레이를 순회하십시오. 요소가 힙의 첫 번째 요소보다 작 으면 교체하십시오.

InSERT ELEMENT INTO HEAP: O(log100)
compare the first element: O(1)
There are n elements in the array, so the total would be O(nlog100), which is O(n)

(2) 맵 축소 모델.

이것은 hadoop의 단어 수 예제와 매우 유사합니다. 지도 작업 : 모든 요소의 빈도 또는 출현 횟수를 계산합니다. 축소 : 상위 K 요소를 얻습니다.

일반적으로 모집 담당자에게 두 가지 답변을 제공합니다. 그들이 원하는 것을 줘. 물론 맵 축소 코딩은 모든 정확한 매개 변수를 알아야하기 때문에 힘들 수 있습니다. 연습해도 해가되지 않습니다. 행운을 빕니다.


MapReduce의 +1, 당신이 10 억 개의 숫자로 하둡을 언급 한 유일한 사람이라고 믿을 수는 없습니다. 면접관이 10 억 개의 숫자를 요청하면 어떻게됩니까? 내 의견으로는 더 많은 표를받을 자격이 있습니다.
Silviu Burcea

@Silviu Burcea 감사합니다. MapReduce도 중요하게 생각합니다. :)
Chris Su

이 예제에서 100의 크기는 일정하지만 실제로이를 별도의 변수 즉, 일반화해야합니다. 케이. 100이 10 억만큼이나 일정하기 때문에 왜 큰 숫자 집합의 크기에 n의 크기 변수를 주지만 작은 숫자 집합이 아닌가? 실제로 복잡성은 O (n)이 아닌 O (nlogk) 여야합니다.
톰 허드

1
그러나 내 요점은 당신이 그 질문에 답하면 10 억이 질문에 고정되어 있기 때문에 왜 100 억 k가 아닌 10 억 n을 일반화하는 것입니다. 논리에 따르면이 질문에서 10 억과 100이 모두 고정되어 있기 때문에 복잡성은 실제로 O (1)이어야합니다.
Tom Heard

1
@TomHeard 좋습니다. O (nlogk) 결과에 영향을주는 요소는 하나뿐입니다. 즉, n이 점점 커지면 "결과 수준"이 선형으로 증가합니다. 또는 우리는 심지어 1 조 개의 숫자가 있다고해도 100 개의 가장 큰 숫자를 얻을 수 있다고 말할 수 있습니다. 그러나 말할 수 없습니다 : n을 증가 시키면 k가 증가하여 k가 결과에 영향을 미칩니다. 그래서 O (nlogk)는 사용하지 않지만 O (nlogn)는 사용하지 않는 이유
Chris Su

4

매우 쉬운 해결책은 배열을 100 번 반복하는 것입니다. 어느O(n) .

가장 큰 숫자를 뽑을 때마다 (그리고 그 값을 최소값으로 변경하여 다음 반복에서 그것을 보지 못하거나 이전 답변의 색인을 추적하십시오 (원래 배열이 가질 수있는 색인을 추적하여) 같은 숫자의 배수)). 100 번의 반복 후에는 100 개의 가장 큰 숫자가 있습니다.


1
두 가지 단점-(1) 프로세스에서 입력을 파괴하고 있습니다. 피하는 것이 좋습니다. (2) 배열을 여러 번 겪고 있습니다. 배열이 디스크에 저장되어 메모리에 맞지 않으면 허용되는 답변보다 거의 100 배 느릴 수 있습니다. (예, 둘 다 O (n)이지만 여전히)
Bernhard Barker

@Dukeling에게 전화를 걸어, 이전 답변 지수를 추적하여 원래 입력을 변경하지 않는 방법에 대한 문구를 추가했습니다. 여전히 코딩하기가 매우 쉽습니다.
James Oravec

O (n log n)보다 훨씬 느린 O (n) 솔루션의 훌륭한 예입니다. log2 (10 억)는 30에 불과합니다.
gnasher729

@ gnasher729 O (n log n)에 상수가 얼마나 큰가요?
miracle173

1

@ron teller의 답변에서 영감을 얻은 다음은 원하는 것을 수행하는 베어 C 프로그램입니다.

#include <stdlib.h>
#include <stdio.h>

#define TOTAL_NUMBERS 1000000000
#define N_TOP_NUMBERS 100

int 
compare_function(const void *first, const void *second)
{
    int a = *((int *) first);
    int b = *((int *) second);
    if (a > b){
        return 1;
    }
    if (a < b){
        return -1;
    }
    return 0;
}

int 
main(int argc, char ** argv)
{
    if(argc != 2){
        printf("please supply a path to a binary file containing 1000000000"
               "integers of this machine's wordlength and endianness\n");
        exit(1);
    }
    FILE * f = fopen(argv[1], "r");
    if(!f){
        exit(1);
    }
    int top100[N_TOP_NUMBERS] = {0};
    int sorts = 0;
    for (int i = 0; i < TOTAL_NUMBERS; i++){
        int number;
        int ok;
        ok = fread(&number, sizeof(int), 1, f);
        if(!ok){
            printf("not enough numbers!\n");
            break;
        }
        if(number > top100[0]){
            sorts++;
            top100[0] = number;
            qsort(top100, N_TOP_NUMBERS, sizeof(int), compare_function);
        }

    }
    printf("%d sorts made\n"
    "the top 100 integers in %s are:\n",
    sorts, argv[1] );
    for (int i = 0; i < N_TOP_NUMBERS; i++){
        printf("%d\n", top100[i]);
    }
    fclose(f);
    exit(0);
}

내 컴퓨터 (빠른 SSD가 장착 된 코어 i3)에서는 25 초가 걸리고 1724 가지가 있습니다. 나는 바이너리 파일을 생성했다dd if=/dev/urandom/ count=1000000000 bs=1이 실행 위해 .

분명히, 디스크에서 한 번에 4 바이트 만 읽는 데 성능 문제가 있지만, 예를 들어 말입니다. 장점은 메모리가 거의 필요 없다는 것입니다.


1

가장 간단한 해결책은 10 억 개의 큰 배열을 스캔하고 정렬없이 작은 배열 버퍼에서 지금까지 발견 된 100 개의 가장 큰 값을 보유하고이 버퍼의 가장 작은 값을 기억하는 것입니다. 처음에는이 방법이 fordprefect에 의해 제안되었다고 생각했지만 의견에서 그는 100 숫자 데이터 구조가 힙으로 구현되었다고 가정했습니다. 새로운 숫자가 발견 될 때마다 버퍼의 최소값이 발견 된 새 값으로 겹쳐 쓰여 버퍼가 현재 최소값을 다시 검색합니다. 10 억 개의 숫자 배열의 숫자가 대부분 랜덤하게 분포되면 큰 배열의 값이 작은 배열의 최소값과 비교되고 버려집니다. 아주 작은 숫자의 경우에만 값을 작은 배열에 삽입해야합니다. 따라서 적은 수의 데이터 구조를 조작하는 차이를 무시할 수 있습니다. 소수의 요소의 경우 우선 순위 대기열의 사용이 실제로 순진한 접근 방식을 사용하는 것보다 빠른지 판단하기가 어렵습니다.

10 ^ 9 요소 배열을 스캔 할 때 작은 100 요소 배열 버퍼의 삽입 수를 추정하고 싶습니다. 프로그램은이 큰 배열의 첫 1000 개 요소를 스캔하며 최대 1000 개의 요소를 버퍼에 삽입해야합니다. 버퍼는 스캔 된 1000 개의 요소 중 100 개의 요소, 즉 스캔 된 요소의 0.1을 포함합니다. 따라서 큰 배열의 값이 버퍼의 현재 최소값보다 클 확률은 약 0.1이라고 가정합니다. 이러한 요소는 버퍼에 삽입해야합니다. 이제 프로그램은 큰 배열에서 다음 10 ^ 4 요소를 스캔합니다. 새 요소가 삽입 될 때마다 버퍼의 최소값이 증가하기 때문입니다. 현재 최소값보다 큰 요소의 비율은 약 0.1이므로 삽입 할 0.1 * 10 ^ 4 = 1000 요소가 있다고 추정했습니다. 실제로 버퍼에 삽입되는 예상 요소 수는 더 적습니다. 이 10 ^ 4 요소를 스캔 한 후 버퍼에있는 숫자의 일부는 지금까지 스캔 된 요소의 약 0.01이됩니다. 따라서 다음 10 ^ 5 숫자를 스캔 할 때 버퍼에 0.01 * 10 ^ 5 = 1000 이하가 삽입된다고 가정합니다. 이 논증을 계속해서 우리는 큰 배열의 1000 + 10 ^ 4 + 10 ^ 5 + ... + 10 ^ 9 ~ 10 ^ 9 요소를 스캔 한 후 약 7000 개의 값을 삽입했습니다. 따라서 임의 크기의 10 ^ 9 요소로 배열을 스캔 할 때 버퍼에서 10 ^ 4 (= 7000 반올림) 이하의 삽입을 기대합니다. 버퍼에 삽입 한 후 새로운 최소값을 찾아야합니다. 버퍼가 간단한 배열이면 새로운 최소값을 찾기 위해 100 개의 비교가 필요합니다. 버퍼가 다른 데이터 구조 (예 : 힙) 인 경우 최소값을 찾기 위해 적어도 1 개의 비교가 필요합니다. 큰 배열의 요소를 비교하려면 10 ^ 9 비교가 필요합니다. 따라서 배열을 버퍼로 사용할 때 약 10 ^ 9 + 100 * 10 ^ 4 = 1.001 * 10 ^ 9 비교가 필요하고 다른 유형의 데이터 구조 (예 : 힙)를 사용하는 경우 최소 1.000 * 10 ^ 9 비교가 필요합니다. . 따라서 성능을 비교 횟수로 결정하면 힙을 사용하면 0.1 % 만 얻을 수 있습니다. 그러나 100 요소 힙에 요소를 삽입하고 100 요소 배열의 요소를 교체하고 새로운 최소값을 찾는 것의 실행 시간 차이는 무엇입니까? 다른 유형의 데이터 구조 (예 : 힙)를 사용할 때 000 * 10 ^ 9 비교 따라서 성능을 비교 횟수로 결정하면 힙을 사용하면 0.1 % 만 얻을 수 있습니다. 그러나 100 요소 힙에 요소를 삽입하고 100 요소 배열의 요소를 교체하고 새로운 최소값을 찾는 것의 실행 시간 차이는 무엇입니까? 다른 유형의 데이터 구조 (예 : 힙)를 사용할 때 000 * 10 ^ 9 비교 따라서 성능을 비교 횟수로 결정하면 힙을 사용하면 0.1 % 만 얻을 수 있습니다. 그러나 100 요소 힙에 요소를 삽입하고 100 요소 배열의 요소를 교체하고 새로운 최소값을 찾는 것의 실행 시간 차이는 무엇입니까?

  • 이론적 수준에서 : 힙에 삽입하는 데 필요한 비교 횟수입니다. 나는 그것이 O (log (n))라는 것을 알고 있지만 상수 요소는 얼마나 큽니까? 나는

  • 머신 레벨에서 : 캐시 삽입 및 분기 예측이 힙 삽입의 실행 시간 및 어레이의 선형 검색에 미치는 영향은 무엇입니까?

  • 구현 수준 : 라이브러리 또는 컴파일러가 제공하는 힙 데이터 구조에 어떤 추가 비용이 숨겨 집니까?

나는 이것이 100 요소 힙 또는 100 요소 배열의 성능 간의 실제 차이를 추정하기 전에 대답해야 할 몇 가지 질문이라고 생각합니다. 따라서 실험을하고 실제 성능을 측정하는 것이 합리적입니다.


1
그것이 힙이하는 일입니다.
Neil G

@ 닐 G : "그것"은 무엇입니까?
miracle173

1
힙의 맨 위는 힙의 최소 요소이며 새 요소는 한 번의 비교로 거부됩니다.
Neil G

1
나는 당신이 말하는 것을 이해하지만, 점근 적 인 비교 횟수가 아닌 절대적인 비교 횟수로가더라도 "새로운 요소를 삽입하고 오래된 최소값을 버리고 새로운 최소값을 찾는 시간"이 약 7이 아니라 100.
Neil G

1
알았어,하지만 네 추정치는 매우 우회적이야 k (digamma (n)-digamma (k)) 인 klog (n)보다 작은 인서트 수를 직접 계산할 수 있습니다. 어쨌든 힙과 배열 솔루션은 하나의 비교만으로 요소를 버립니다. 유일한 차이점은 삽입 된 요소의 비교 횟수는 솔루션의 경우 100이고 힙의 경우 최대 14입니다 (평균 경우는 훨씬 적지 만)
Neil G

1
 Although in this question we should search for top 100 numbers, I will 
 generalize things and write x. Still, I will treat x as constant value.

알고리즘 n에서 가장 큰 x 요소 :

반환 값 LIST 를 호출합니다 . 그것은 x 요소의 집합입니다 (제 생각에는 링크 된 목록이어야합니다)

  • 첫 번째 x 요소는 "있는 그대로"풀에서 가져와 LIST로 정렬됩니다 (x는 상수-O (x log (x)) 시간으로 처리되므로 상수 시간에 수행됨)
  • 다음에 오는 모든 요소에 대해 LIST에서 가장 작은 요소보다 큰지 확인하고 가장 작은 요소인지 확인하고 현재 요소를 LIST에 삽입합니다. 그것이 정렬 된 목록이기 때문에 모든 요소는 로그 시간 (이진 검색)으로 그 위치를 찾아야하며 정렬되기 때문에 목록 삽입은 문제가되지 않습니다. 모든 단계는 일정한 시간 (O (log (x)) 시간)으로도 수행됩니다.

그렇다면 최악의 시나리오는 무엇입니까?

x log (x) + (nx) (log (x) +1) = nlog (x) + n-x

최악의 경우 O (n) 시간입니다. +1은 LIST에서 숫자가 가장 작은 숫자보다 큰지 여부를 확인하는 것입니다. 평균 사례에 대한 예상 시간은 해당 n 요소의 수학적 분포에 따라 달라집니다.

가능한 개선

이 알고리즘은 최악의 시나리오에서는 약간 개선 될 수 있지만 평균 동작을 저하시키는 IMHO (이 주장을 증명할 수 없음)입니다. 점근 적 행동은 동일합니다.

이 알고리즘의 개선은 요소가 가장 작은 지 여부를 확인하지 않을 것입니다. 각 요소에 대해 삽입을 시도하고 가장 작은 것보다 작 으면 무시합니다. 최악의 시나리오 만 고려한다면 그것은 터무니없는 것처럼 들리지만

x log (x) + (nx) log (x) = nlog (x)

작업.

이 유스 케이스의 경우 더 이상 개선되지 않습니다. 그러나 당신은 스스로에게 물어봐야합니다-log (n) 번 이상 다른 x-es를 위해 이것을해야한다면 어떻게해야합니까? 분명히 우리는 그 배열을 O (n log (n))로 정렬하고 필요할 때마다 x 요소를 가져옵니다.


1

이 질문은 단 한 줄의 C ++ 코드로 N log (100) 복잡성 (N log N 대신)으로 대답합니다.

 std::vector<int> myvector = ...; // Define your 1 billion numbers. 
                                 // Assumed integer just for concreteness 
 std::partial_sort (myvector.begin(), myvector.begin()+100, myvector.end());

마지막 대답은 처음 100 개의 요소가 100 개의 가장 큰 숫자 배열이고 나머지 요소는 정렬되지 않은 벡터입니다.

C ++ STL (표준 라이브러리)은 이런 종류의 문제에 매우 편리합니다.

참고 : 이것이 최적의 해결책이라고 말하지는 않지만 인터뷰를 저장했을 것입니다.


1

간단한 해결책은 우선 순위 대기열을 사용하여 대기열에 처음 100 개의 숫자를 추가하고 대기열에서 가장 작은 숫자를 추적 한 다음 다른 10 억 숫자를 반복하고 가장 큰 숫자보다 큰 숫자를 찾을 때마다 우선 순위 대기열에서 가장 작은 번호를 제거하고 새 번호를 추가 한 다음 다시 대기열에서 가장 작은 번호를 추적합니다.

숫자가 임의의 순서로되어 있으면 10 억 개의 임의의 숫자를 반복 할 때 다음 숫자가 지금까지 가장 큰 100 개 중 하나가되기 때문에 매우 아름답습니다. 그러나 숫자는 임의적이지 않을 수 있습니다. 배열이 이미 오름차순으로 정렬되어 있으면 항상 우선 순위 큐에 요소를 삽입합니다.

먼저 배열에서 100,000 개의 난수를 선택 합니다. 속도가 느릴 수있는 임의 액세스를 피하기 위해 250 개의 연속 숫자로 구성된 400 개의 임의 그룹을 추가합니다. 임의의 선택을 통해 나머지 숫자 중 100 개가 최상위에 있음을 확신 할 수 있으므로 실행 시간은 10 억 개의 숫자를 최대 값과 비교하는 간단한 루프의 시간에 매우 가깝습니다.


1

10 억 개의 숫자 중 상위 100 개를 찾는 것이 최소 힙을 사용하는 것이 가장 좋습니다. 100 개 요소의 을 .

먼저 처음 100 개의 숫자로 최소 힙을 채 웁니다. 최소 힙은 처음 100 개의 숫자 중 가장 작은 것을 루트 (상단)에 저장합니다.

이제 나머지 숫자를 따라 가면 루트 (100 중 가장 작은 것) 와만 비교됩니다.

새로운 숫자가 min-heap의 루트보다 큰 경우 루트를 해당 숫자로 바꾸십시오. 그렇지 않으면 무시하십시오.

최소 힙에 새 숫자를 삽입하는 과정에서 힙에서 가장 작은 숫자가 맨 위에 오게됩니다 (루트).

모든 숫자를 다 살펴보면 최소 힙에서 가장 큰 숫자가 100이됩니다.


0

누군가 관심이있는 경우 Python에서 간단한 솔루션을 작성했습니다. 그것은 사용하는 bisect모듈과 정렬 계속 임시 반환 목록. 이것은 우선 순위 큐 구현과 유사합니다.

import bisect

def kLargest(A, k):
    '''returns list of k largest integers in A'''
    ret = []
    for i, a in enumerate(A):
        # For first k elements, simply construct sorted temp list
        # It is treated similarly to a priority queue
        if i < k:
            bisect.insort(ret, a) # properly inserts a into sorted list ret
        # Iterate over rest of array
        # Replace and update return array when more optimal element is found
        else:
            if a > ret[0]:
                del ret[0] # pop min element off queue
                bisect.insort(ret, a) # properly inserts a into sorted list ret
    return ret

정렬 된 목록 인 100,000,000 개의 요소와 최악의 입력을 가진 사용법 :

>>> from so import kLargest
>>> kLargest(range(100000000), 100)
[99999900, 99999901, 99999902, 99999903, 99999904, 99999905, 99999906, 99999907,
 99999908, 99999909, 99999910, 99999911, 99999912, 99999913, 99999914, 99999915,
 99999916, 99999917, 99999918, 99999919, 99999920, 99999921, 99999922, 99999923,
 99999924, 99999925, 99999926, 99999927, 99999928, 99999929, 99999930, 99999931,
 99999932, 99999933, 99999934, 99999935, 99999936, 99999937, 99999938, 99999939,
 99999940, 99999941, 99999942, 99999943, 99999944, 99999945, 99999946, 99999947,
 99999948, 99999949, 99999950, 99999951, 99999952, 99999953, 99999954, 99999955,
 99999956, 99999957, 99999958, 99999959, 99999960, 99999961, 99999962, 99999963,
 99999964, 99999965, 99999966, 99999967, 99999968, 99999969, 99999970, 99999971,
 99999972, 99999973, 99999974, 99999975, 99999976, 99999977, 99999978, 99999979,
 99999980, 99999981, 99999982, 99999983, 99999984, 99999985, 99999986, 99999987,
 99999988, 99999989, 99999990, 99999991, 99999992, 99999993, 99999994, 99999995,
 99999996, 99999997, 99999998, 99999999]

이것을 100,000,000 요소로 계산하는 데 약 40 초가 걸렸으므로 10 억 동안 그것을 두려워합니다. 그러나 공정하게 말해서 최악의 입력 (이론적으로 이미 정렬 된 배열)을 공급하고있었습니다.


0

나는 많은 O (N) 토론을 봅니다. 그래서 나는 생각 연습을 위해서 다른 것을 제안합니다.

이 숫자의 특성에 대한 알려진 정보가 있습니까? 그것이 무작위라면, 더 이상 가지 말고 다른 대답을보십시오. 당신은 그들보다 더 나은 결과를 얻을 수 없습니다.

하나! 목록을 채우는 메커니즘이 특정 순서로 해당 목록을 채우는 지 확인하십시오. 그것들은 목록의 특정 영역이나 특정 간격에서 가장 큰 숫자의 숫자가 발견 될 것임을 확실하게 알 수있는 잘 정의 된 패턴입니까? 패턴이있을 수 있습니다. 그렇다면, 예를 들어 중간에 특징적인 혹이있는 정규 분포가 보장되는 경우, 정의 된 부분 집합 사이에서 항상 상승 추세가 반복되고, 데이터 중간의 어느 시점에서 T의 시간이 길어질 수 있습니다. 내부자 거래 또는 장비 고장의 발생률처럼 설정되거나, 재난 후의 힘 분석에서와 같이 N 번째 숫자마다 "스파이크"가 발생했을 경우, 확인해야 할 레코드 수를 크게 줄일 수 있습니다.

어쨌든 생각할 음식이 있습니다. 어쩌면 이것은 미래 면접관에게 신중한 답변을 제공하는 데 도움이 될 것입니다. 누군가가 이와 같은 문제에 대한 응답으로 나에게 그런 질문을하면 감동받을 것입니다. 최적화를 생각하고 있다고 말할 것입니다. 항상 최적화 할 수있는 것은 아닙니다.


0
Time ~ O(100 * N)
Space ~ O(100 + N)
  1. 100 개의 빈 슬롯으로 구성된 빈 목록 만들기

  2. 입력 목록의 모든 숫자에 대해 :

    • 숫자가 첫 번째 숫자보다 작 으면 건너 뛰십시오.

    • 그렇지 않으면이 번호로 바꾸십시오

    • 그런 다음 인접한 스왑을 통해 번호를 푸시하십시오. 다음 것보다 작아 질 때까지

  3. 목록을 반환


참고 : 이면 log(input-list.size) + c < 100최적의 방법은 입력 목록을 정렬 한 다음 처음 100 개 항목을 분할하는 것입니다.


0

복잡도는 O (N)

먼저 100 int의 배열을 생성 하여이 배열의 첫 번째 요소를 N 값의 첫 번째 요소로 초기화하고 다른 변수를 사용하여 현재 요소의 색인을 추적하고 CurrentBig

N 값을 반복

if N[i] > M[CurrentBig] {

M[CurrentBig]=N[i]; ( overwrite the current value with the newly found larger number)

CurrentBig++;      ( go to the next position in the M array)

CurrentBig %= 100; ( modulo arithmetic saves you from using lists/hashes etc.)

M[CurrentBig]=N[i];    ( pick up the current value again to use it for the next Iteration of the N array)

} 

완료되면 CurrentBig에서 M 배열 100을 모듈로 100의 100 배로 인쇄합니다. :-) 학생의 경우 : 코드의 마지막 줄이 코드가 종료되기 직전에 유효한 데이터보다 우선하지 않도록하십시오


0

또 다른 O (n) 알고리즘-

알고리즘은 제거하여 가장 큰 100을 찾습니다.

이진 표현에서 모든 백만 개의 숫자를 고려하십시오. 가장 중요한 비트부터 시작하십시오. MSB가 1인지 확인하는 것은 적절한 수의 부울 연산 곱셈에 의해 수행 될 수 있습니다. 이 백만 개에 100 개가 넘는 1이 있으면 0으로 다른 숫자를 제거하십시오. 이제 나머지 숫자 중 다음으로 가장 중요한 비트가 진행됩니다. 제거 후 남은 숫자의 수를 세고이 숫자가 100보다 큰 한 계속하십시오.

주요 부울 연산은 GPU에서 병렬로 수행 될 수 있습니다.


0

나는 누가 10 억의 숫자를 배열에 넣고 그를 해고 할 시간이 있었는지 알아낼 것입니다. 정부를 위해 일해야합니다. 최소한 연결된 목록이 있다면 50 억을 움직이지 않고 중간에 숫자를 삽입하여 공간을 만들 수 있습니다. 더 나은 Btree는 이진 검색을 허용합니다. 각 비교는 총계의 절반을 제거합니다. 해시 알고리즘을 사용하면 바둑판과 같은 데이터 구조를 채울 수 있지만 스파 스 데이터에는 적합하지 않습니다. 가장 좋은 방법은 정수 100의 솔루션 배열을 가지고 솔루션 배열에서 가장 낮은 숫자를 추적하여 원래 배열에서 더 높은 숫자를 발견 할 때 교체 할 수 있다는 것입니다. 처음 배열로 정렬되지 않았다고 가정하면 원래 배열의 모든 요소를 ​​살펴 봐야합니다.


0

당신은 O(n)시간에 그것을 할 수 있습니다 . 목록을 반복하고 특정 지점에서 본 100 개의 가장 큰 숫자와 해당 그룹의 최소값을 추적하십시오. 새로운 숫자가 10보다 작은 것보다 큰 것을 발견하면, 그것을 바꾸고 100의 새로운 최소값을 업데이트하십시오 (100을 할 때마다이를 결정하기 위해 일정한 시간이 걸릴 수 있지만 전체 분석에는 영향을 미치지 않습니다) ).


1
이 접근법은이 질문에 대한 가장 많이 그리고 두 번째로 많이지지 된 답변과 거의 동일합니다.
Bernhard Barker

0

별도의 목록을 관리하는 것은 추가 작업이므로 다른 대체품을 찾을 때마다 전체 목록을 이동해야합니다. 그냥 qsort하고 100을 차지하십시오.


-1 quicksort는 O (n log n)이며 이는 OP가 수행 한 것과 정확히 일치하는 것입니다. 별도의 목록을 관리 할 필요가없고 100 개의 숫자 목록 만 관리하면됩니다. 귀하의 제안은 또한 원래 목록을 변경하거나 복사 할 때 바람직하지 않은 부작용이 있습니다. 4GiB 정도의 메모리가 사라졌습니다.

0
  1. n 번째 요소를 사용하여 100 번째 요소 O (n)
  2. 두 번 반복하지만 한 번만 반복하고이 특정 요소보다 큰 모든 요소를 ​​출력하십시오.

esp. 두 번째 단계는 병렬로 쉽게 계산할 수 있습니다! 또한 백만 개의 가장 큰 요소가 필요할 때에도 효율적입니다.


0

Google 또는 다른 업계 거인의 질문입니다. 면접관이 예상하는 정답은 다음 코드 일 수 있습니다. 시간 비용 및 공간 비용은 입력 배열의 최대 수에 따라 다릅니다 .32 비트 정수 배열 입력의 경우 최대 공간 비용은 4 * 125M 바이트이고 시간 비용은 5 * 십억입니다.

public class TopNumber {
    public static void main(String[] args) {
        final int input[] = {2389,8922,3382,6982,5231,8934
                            ,4322,7922,6892,5224,4829,3829
                            ,6892,6872,4682,6723,8923,3492};
        //One int(4 bytes) hold 32 = 2^5 value,
        //About 4 * 125M Bytes
        //int sort[] = new int[1 << (32 - 5)];
        //Allocate small array for local test
        int sort[] = new int[1000];
        //Set all bit to 0
        for(int index = 0; index < sort.length; index++){
            sort[index] = 0;
        }
        for(int number : input){
            sort[number >>> 5] |= (1 << (number % 32));
        }
        int topNum = 0;
        outer:
        for(int index = sort.length - 1; index >= 0; index--){
            if(0 != sort[index]){
                for(int bit = 31; bit >= 0; bit--){
                    if(0 != (sort[index] & (1 << bit))){
                        System.out.println((index << 5) + bit);
                        topNum++;
                        if(topNum >= 3){
                            break outer;
                        }
                    }
                }
            }
        }
    }
}

0

나는 "인터뷰어"가 무엇을보고 있는지 잘 모르겠다.

private static final int MAX=100;
 PriorityQueue<Integer> queue = new PriorityQueue<>(MAX);
        queue.add(array[0]);
        for (int i=1;i<array.length;i++)
        {

            if(queue.peek()<array[i])
            {
                if(queue.size() >=MAX)
                {
                    queue.poll();
                }
                queue.add(array[i]);

            }

        }

0

가능한 개선.

파일에 10 억 개의 숫자가 포함되어 있으면 파일을 읽는 데 시간 오래 걸릴 수 있습니다 .

이 작업을 개선하려면 다음을 수행하십시오.

  • 파일을 n 부분으로 나누고 n 개의 스레드를 만들고 n 개의 스레드가 우선 순위 대기열을 사용하여 파일 부분에서 100 개의 가장 큰 숫자를 찾도록하고 마지막으로 모든 스레드 출력의 100 가장 큰 수를 얻습니다.
  • hadoop과 같은 솔루션으로 클러스터를 사용하여 이러한 작업을 수행하십시오. 여기에서 파일을 더 많이 분할하고 10 억 (또는 10 ^ 12) 숫자 파일에 대해 더 빠르게 출력 할 수 있습니다.

0

먼저 1000 개의 요소를 가져 와서 최대 힙에 추가하십시오. 이제 첫 번째 최대 100 개 요소를 꺼내 어딘가에 저장하십시오. 이제 파일에서 다음 900 개의 요소를 선택하여 마지막 100 개의 최상위 요소와 함께 힙에 추가하십시오.

힙에서 100 개의 요소를 선택하고 파일에서 900 개의 요소를 추가하는이 프로세스를 계속 반복하십시오.

100 개의 요소를 최종 선택하면 10 억 개의 숫자 중 최대 100 개의 요소가 제공됩니다.


-1

문제 : n >>> m에서 n 개의 항목 중 가장 큰 m 개의 요소를 찾습니다.

모든 사람에게 명백한 가장 간단한 해결책은 간단히 버블 정렬 알고리즘을 m 패스하는 것입니다.

그런 다음 배열의 마지막 n 개 요소를 인쇄하십시오.

외부 데이터 구조가 필요하지 않으며 모든 사람이 알고있는 알고리즘을 사용합니다.

실행 시간 추정치는 O (m * n)입니다. 지금까지 가장 좋은 대답은 O (n log (m))이므로이 솔루션은 작은 m에 비해 크게 비싸지 않습니다.

나는 이것이 개선 될 수 없다고 말하지는 않지만 이것이 가장 간단한 해결책입니다.


1
외부 데이터 구조가 없습니까? 정렬 할 수십억 개의 배열은 어떻습니까? 이 크기의 배열은 채울 시간과 저장 공간 모두에서 엄청난 오버 헤드입니다. 모든 "큰"숫자가 배열의 잘못된 끝에 있으면 어떻게합니까? 당신은 그것들을 "버블 링 (bubble)"하기 위해 1000 억 스왑을 필요로 할 것이다.-또 다른 큰 오버 헤드 ... 마지막으로, M N = 1000 억 대 M Log2 (N) = 6.64 십억은 거의 2 배의 차이가있다. 아마도 이것을 다시 생각하십시오. 가장 많은 수의 데이터 구조를 유지하면서 한 번의 스캔으로이 접근 방식을 크게 능가 할 것입니다.
NealB
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.