주어진 40 억 사이가 아닌 정수를 생성


692

이 면접 질문을 받았습니다 :

40 억 개의 정수를 가진 입력 파일이 주어지면 파일에 포함되지 않은 정수를 생성하는 알고리즘을 제공하십시오. 1GB 메모리가 있다고 가정하십시오. 10MB의 메모리 만있는 경우 수행 할 작업을 추적하십시오.

내 분석 :

파일 크기는 4 × 10 9 × 4 bytes = 16GB입니다.

외부 정렬을 수행 할 수 있으므로 정수 범위를 알려줍니다.

내 질문은 정렬 된 큰 정수 세트에서 누락 된 정수를 감지하는 가장 좋은 방법은 무엇입니까?

내 이해 (모든 답변을 읽은 후) :

32 비트 정수에 대해 이야기하고 있다고 가정하면 2 32 = 4 * 10 9 개의 고유 정수가 있습니다.

사례 1 : 1GB = 1 * 10 9 * 8 비트 = 80 억 비트 메모리가 있습니다.

해결책:

하나의 고유 한 정수를 나타내는 하나의 비트를 사용하면 충분합니다. 우리는 정렬 할 필요가 없습니다.

이행:

int radix = 8;
byte[] bitfield = new byte[0xffffffff/radix];
void F() throws FileNotFoundException{
    Scanner in = new Scanner(new FileReader("a.txt"));
    while(in.hasNextInt()){
        int n = in.nextInt();
        bitfield[n/radix] |= (1 << (n%radix));
    }

    for(int i = 0; i< bitfield.lenght; i++){
        for(int j =0; j<radix; j++){
            if( (bitfield[i] & (1<<j)) == 0) System.out.print(i*radix+j);
        }
    }
}

사례 2 : 10MB 메모리 = 10 * 10 6 * 8 비트 = 8 천만 비트

해결책:

가능한 모든 16 비트 접두사에 대해 2 16 개의 정수 = 65536이 있으며 2 16 * 4 * 8 = 2 백만 비트가 필요합니다. 65536 버킷을 빌드해야합니다. 각 버킷마다 모든 가능성을 보유하는 4 바이트가 필요합니다. 최악의 경우 40 억 정수가 모두 동일한 버킷에 속하기 때문입니다.

  1. 파일을 통한 첫 번째 통과를 통해 각 버킷의 카운터를 만듭니다.
  2. 버킷을 스캔하고 적중률이 65536 미만인 첫 번째 버킷을 찾으십시오.
  3. 2 단계에서 파일의 두 번째 패스를 통해 높은 16 비트 접두사가있는 새 버킷을 빌드하십시오.
  4. 3 단계에서 빌드 한 버킷을 스캔하고 적중하지 않은 첫 번째 버킷을 찾으십시오.

코드는 위의 코드와 매우 유사합니다.

결론 : 파일 전달을 늘려 메모리를 줄입니다.


늦게 도착하는 사람들을위한 설명 : 질문에 따르면, 질문에 따르면 파일에 포함되지 않은 정수가 정확히 하나 있다고 말하지는 않습니다. 적어도 대부분의 사람들이 그것을 해석하는 방식이 아닙니다. 주석 스레드의 많은 의견이 있다 하지만, 작업의 변화에 대해. 불행하게도 주석 도입 이후 저자에 의해 삭제 된 코멘트 스레드에, 그래서 지금은 오해 다 그것에 고아 응답 것 같습니다. 매우 혼란 스럽습니다. 죄송합니다.


32
@trashgod, 잘못되었습니다. 4294967295 고유 정수의 경우 1 개의 정수가 남습니다. 그것을 찾으려면 모든 정수를 합산하고 가능한 모든 정수의 미리 계산 된 합에서 빼야합니다.
Nakilon

58
이것은 "Programming Pearls"의 두 번째 "pearl"이며,이 책에서 모든 토론을 읽도록 제안합니다. 참조 books.google.com/...
알록 Singhal이에게

8
@Richard 64 비트 int는 충분히 큰 것입니다.
cftarnas

79
int getMissingNumber(File inputFile) { return 4; }( 참조 )
johnny

14
C / C ++와 같은 언어의 정수 유형은 항상 연관성 및 통신 성과 같은 속성을 유지하므로 1에서 2 ^ 32까지의 모든 정수의 합을 저장할 수는 없습니다. 이것이 의미하는 바는 합이 정답은 아니지만 오버플로로 예상되는 금액, 오버플로로 실제 합계를 뺀 다음 빼더라도 결과는 여전히 정확하다는 것입니다 (자체가 오버플로되지 않는 경우).
dayturns

답변:


530

"정수"가 32 비트를 의미한다고 가정하면, 10MB의 공간이 주어진 16 비트 접두사를 가진 입력 파일에 몇 개의 숫자가 있는지 계산하기에 충분합니다. 입력 파일. 버킷 중 하나 이상이 2 16 회 미만으로 적중 되었습니다. 해당 버킷에서 사용 가능한 숫자 중 어떤 것이 이미 사용 중인지 확인하려면 두 번째 패스를 수행하십시오.

32 비트를 초과하지만 여전히 경계 크기 인 경우 : 부호있는 또는 부호없는, 선택한 32 비트 범위를 벗어나는 모든 입력 숫자를 무시하고 위와 같이하십시오.

"정수"가 수학 정수를 의미하는 경우 : 입력을 한 번 읽고 읽은 가장 긴 숫자 의 최대 길이를 추적하십시오 . 완료되면 최대 자릿수에 숫자 하나가 더 많은 난수를 더한 값을 출력 하십시오 . (파일의 숫자 중 하나는 정확하게 나타내는 데 10MB 이상이 걸리는 큰 숫자 일 수 있지만 입력이 파일 인 경우 최소한 파일에 맞는 길이 를 나타낼 수 있습니다 ).


24
완전한. 첫 번째 답변은 파일을 2 번만 통과하면됩니다!
corsiKa

47
10MB 큰 숫자? 꽤 극단적입니다.
Mark Ransom

12
@Legate, 너무 큰 숫자를 건너 뛰고 아무것도하지 마십시오. 어쨌든 큰 숫자를 출력하지 않기 때문에 본 숫자를 추적 할 필요가 없습니다.
hmakholm 님이 Monica at August

12
솔루션 1의 장점은 패스를 늘려 메모리를 줄일 수 있다는 것입니다.
Yousf

11
@Barry : 위의 질문은 정확히 하나의 숫자가 없음을 나타내지 않습니다. 파일의 숫자도 반복되지 않는다고 말하지 않습니다. (실제로 묻는 질문을 따르는 것이 인터뷰에서 좋은 생각 일 것입니다. ;-))
Christopher Creutzig

197

통계 정보 알고리즘은 결정적 접근 방식보다 적은 패스를 사용하여이 문제를 해결합니다.

매우 큰 정수가 허용되면 O (1) 시간에 고유 한 숫자를 생성 할 수 있습니다. GUID 와 같은 의사 난수 128 비트 정수는 640 억 억 건 중 1 개 미만의 집합에서 기존의 40 억 정수 중 하나와 만 충돌합니다.

정수가 32 비트로 제한되면 10MB보다 훨씬 적은 단일 패스에서 고유 한 숫자를 생성 할 수 있습니다. 의사 랜덤 32 비트 정수가 40 억 개의 기존 정수 중 하나와 충돌 할 확률은 약 93 % (4e9 / 2 ^ 32)입니다. 1000 개의 의사 난수 정수가 모두 충돌 할 확률은 1 천 2 백억 억 (1 개의 충돌 률 ^ 1000) 중 1 개 미만입니다. 따라서 프로그램이 1000 개의 의사 랜덤 후보를 포함하는 데이터 구조를 유지하고 알려진 정수를 반복하여 후보에서 일치하는 항목을 제거하는 경우 파일에없는 하나 이상의 정수를 찾는 것이 확실합니다.


32
정수가 제한되어 있다고 확신합니다. 그렇지 않은 경우 초보자 프로그래머조차도 알고리즘이 "최대 수를 찾기 위해 데이터를 한 번 통과하고 1을 더합니다"
Adrian Petrescu

12
말 그대로 무작위 출력을 추측하면 인터뷰에서 많은 점을 얻지 못할 것입니다.
Brian Gordon

6
@Adrian, 귀하의 솔루션은 명백해 보였고 (나 자신에게 답을 사용했습니다) 모든 사람에게 분명하지는 않습니다. 명백한 해결책을 찾을 수 있는지 또는 만지는 모든 것을 지나치게 복잡하게 만드는지 확인하는 것이 좋습니다.
마크 랜섬

19
@ 브라이언 : 나는이 솔루션이 상상적이고 실용적이라고 생각합니다. 나는이 답변에 많은 찬사를 줄 것입니다.
Richard H

6
아 여기 엔지니어와 과학자 사이의 경계선이 있습니다. 큰 대답 벤!
TrojanName

142

이 문제에 대한 자세한 설명은 Jon Bentley "Column 1. Cracking the Oyster" Programming Pearls Addison-Wesley pp.3-10에서 논의되었습니다.

벤틀리 정렬 등 여러 외부 파일을 사용하여 외부 정렬, 병합 등 여러 가지 방법을 설명하지만 벤틀리가 제안하는 가장 좋은 방법은 사용하는 단일 패스 알고리즘 비트 필드 그는 익살 "정렬 원더"호출 :) 문제에 40 억 오는 숫자는 다음과 같이 나타낼 수 있습니다.

4 billion bits = (4000000000 / 8) bytes = about 0.466 GB

비트 셋을 구현하는 코드는 간단하다 : ( solutions page 에서 가져온 )

#define BITSPERWORD 32
#define SHIFT 5
#define MASK 0x1F
#define N 10000000
int a[1 + N/BITSPERWORD];

void set(int i) {        a[i>>SHIFT] |=  (1<<(i & MASK)); }
void clr(int i) {        a[i>>SHIFT] &= ~(1<<(i & MASK)); }
int  test(int i){ return a[i>>SHIFT] &   (1<<(i & MASK)); }

Bentley의 알고리즘은 파일에서 단일 패스를 생성하여 set배열의 해당 비트를 팅한 다음 test위의 매크로를 사용하여이 배열을 검사 하여 누락 된 숫자를 찾습니다.

사용 가능한 메모리가 0.466GB 미만인 경우 Bentley는 k-pass 알고리즘을 제안합니다.이 알고리즘은 입력을 사용 가능한 메모리에 따라 범위로 나눕니다. 아주 간단한 예를 들어, 1 바이트 (즉, 8 개의 숫자를 처리하는 메모리) 만 사용할 수 있고 범위가 0 ~ 31 인 경우이를 0 ~ 7, 8-15, 16-22 등의 범위로 나눕니다. 각 32/8 = 4패스 에서이 범위를 처리하십시오 .

HTH.


12
이 책을 모르지만 1 비트 카운터가있는 버킷 정렬이므로 "원더 정렬"이라고 부르는 이유가 없습니다.
flolo

3
이식성이 뛰어나지 만이 코드는 하드웨어 지원 벡터 명령어를 사용하도록 작성된 코드에 의해 소멸 됩니다 . gcc는 경우에 따라 벡터 연산을 사용하여 코드를 자동으로 변환 할 수 있다고 생각합니다.
Brian Gordon

3
@ brian 나는 Jon Bentley가 알고리즘에 관한 그의 책에 그러한 것들을 허용하고 있다고 생각하지 않습니다.
David Heffernan

8
@BrianGordon, 램에서 보낸 시간은 파일을 읽는 데 걸린 시간과 비교하여 무시할 수 있습니다. 최적화에 대해 잊어 버리십시오.
Ian

1
@BrianGordon : 아니면 첫 번째 설정되지 않은 비트를 찾기 위해 마지막 루프에 대해 이야기하고 있었습니까? 예, 벡터는 속도를 높이지만 64 비트 정수로 비트 필드를 루핑 != -1하여 단일 코어에서 실행되는 메모리 대역폭을 포화 상태로 만듭니다 (이것은 SIMAR-in-a-register, SWAR, 비트는 요소로 나타남). (최근 인텔 / AMD 디자인). 64 비트 위치를 찾은 후에 설정 해제 된 비트 만 파악하면됩니다. (그리고 당신이 할 수있는 그것에 대해 not / lzcnt.) 하나의 비트 테스트를 통해 반복하는 것이 아니라 최적화되지 않을 수 있음을 공정 점을.
Peter Cordes

120

문제는 파일에없는 가장 작은 가능한 숫자를 찾아야한다고 지정하지 않기 때문에 입력 파일 자체보다 긴 숫자 만 생성 할 수 있습니다. :)


6
파일에서 가장 큰 숫자가 max int가 아니라면 오버플로가 발생합니다
KBusc

실제 정수 프로그램에서 새 정수를 생성하여 "사용 된 정수"파일에 100 번 추가해야하는 파일의 크기는 얼마입니까?
Michael

2
나는 이것을 생각하고 있었다. 가정은 int32비트로 출력 단 2^64-1. 끝난.
imallett

1
행당 하나의 int 인 경우 : tr -d '\n' < nums.txt > new_num.txt: D
Shon

56

1GB RAM 변형의 경우 비트 벡터를 사용할 수 있습니다. 40 억 비트 == 500MB 바이트 배열을 할당해야합니다. 입력에서 읽은 각 숫자에 대해 해당 비트를 '1'로 설정하십시오. 완료되면 비트를 반복하여 여전히 '0'인 첫 번째 비트를 찾으십시오. 그 색인이 답입니다.


4
입력의 숫자 범위가 지정되지 않았습니다. 입력 값이 80 억에서 160 억 사이의 모든 짝수로 구성된 경우이 알고리즘은 어떻게 작동합니까?
Mark Ransom

27
@Mark, 0..2 ^ 32 범위를 벗어난 입력은 무시하십시오. 어쨌든 그것들을 출력하지 않을 것이므로 피해야 할 것을 기억할 필요가 없습니다.
hmakholm

@ 32 비트 문자열이 실수에 어떻게 매핑되는지 결정하는 데 사용하는 알고리즘을 표시하십시오. 프로세스는 여전히 동일합니다. 유일한 차이점은 화면에 실수로 인쇄하는 방법입니다.
corsiKa

4
download.oracle.com/javase/6/docs/api/java/util/…을 사용하는 대신 반복 할 수 있습니다 bitSet.nextClearBit(0).
starblue

3
정수의 범위에 관계없이 패스의 끝에서 적어도 하나의 비트가 0이되도록 보장하는 것이 좋습니다. 이것은 비둘기 구멍 원리 때문입니다.
Rafał Dowgird

46

32 비트 정수인 경우 (2 32에 가까운 ~ 40 억 숫자 선택에서와 같이) 40 억 숫자 의 목록은 가능한 정수의 최대 93 %를 차지합니다 (4 * 10 9 / (2 32 ) ). 따라서 각 비트가 0으로 초기화 된 2 32 비트의 비트 배열을 만들면 (2 29 바이트 ~ 500 MB의 RAM이 필요합니다. 바이트 = 2 3을 기억하십시오. 비트 = 8 비트를 하십시오) 정수 목록을 읽고 각각의 int에 대해 대응하는 비트 어레이 요소를 0 내지 1로 설정하고; 그런 다음 비트 배열을 읽고 여전히 0 인 첫 번째 비트를 반환하십시오.

RAM이 적 으면 (~ 10MB)이 솔루션을 약간 수정해야합니다. 10 MB ~ 83886080 비트는 여전히 0에서 83886079 사이의 모든 숫자에 대해 비트 배열을 수행하기에 충분합니다. 따라서 int 목록을 읽을 수 있습니다. 비트 배열에서 0에서 83886079 사이의 레코드 번호 만 기록하십시오. 숫자가 무작위로 분포 된 경우; (이 약 100 %의 차이 확률이 압도적으로 (10) -2592069 )를) 누락 INT를 찾을 수 있습니다. 실제로 1에서 2048까지의 숫자 (256 바이트의 RAM 만 있음) 만 선택하면 시간의 압도적 비율 (99.99999999999999999999999999999999999999999999999999999999999995 %)이 여전히 누락 된 숫자임을 알 수 있습니다.

그러나 약 40 억 개의 숫자 대신에 2 32-1 숫자와 10MB 미만의 RAM이 있습니다. 따라서 작은 범위의 정수는 숫자를 포함하지 않을 가능성이 적습니다.

당신이 목록의 각 INT 고유 것을 보장한다면, 당신은 숫자를 합계 한 # 전체 합계 (½)에 누락 된 합을 빼기 수 (2 32 ) (2 32 - 1)의 누락 INT를 찾을 수 = 9223372034707292160 . 그러나 int가 두 번 발생하면이 방법은 실패합니다.

그러나 항상 나누고 정복 할 수 있습니다. 순진한 방법은 배열을 읽고 전반 (0 ~ 2 31-1 )과 후반 (2 31 , 2 32 )에 있는 숫자의 수를 세는 것 입니다. 그런 다음 더 적은 수의 범위를 선택하고 해당 범위를 반으로 나누십시오. (말 (2 적은 수의 두이 있다면 (31) , 2 (32) ) 다음, 다음 검색은 범위의 숫자 (2 카운트 것 (31) , 3 * 2 30 -1), (3 * 2 (30) , 2 (32) ). 계속 숫자가 0 인 범위를 찾을 때까지 반복하고 답을 얻으십시오. 배열을 통해 O (lg N) ~ 32 읽기를 가져와야합니다.

그 방법은 비효율적이었습니다. 각 단계에서 2 개의 정수만 사용하거나 4 바이트 (32 비트) 정수를 갖는 약 8 바이트의 RAM을 사용합니다. 더 나은 방법은 sqrt (2 32 ) = 2 16 = 65536 bin 으로 나누는 것입니다 . 각 bin에는 65536 개의 숫자가 있습니다. 각 저장소에는 카운트를 저장하는 데 4 바이트가 필요하므로 2 18 바이트 = 256kB 가 필요합니다 . 빈 0이되어, (0 (2) = 65535 (16) -1), 빈 1 (2, 16 = 65536 (2) * 2 (16) , 빈 (2) -1 = 131,071) (2 * 2 16 = 131072 3 2 * 16 - 1 = 196607). 파이썬에서는 다음과 같은 것이 있습니다.

import numpy as np
nums_in_bin = np.zeros(65536, dtype=np.uint32)
for N in four_billion_int_array:
    nums_in_bin[N // 65536] += 1
for bin_num, bin_count in enumerate(nums_in_bin):
    if bin_count < 65536:
        break # we have found an incomplete bin with missing ints (bin_num)

~ 40 억 개의 정수 목록을 읽습니다. 2 개의 16 개 빈 각각에 몇 개의 정수가 있는지 계산 하고 65536 개의 숫자가 모두없는 incomplete_bin을 찾으십시오. 그런 다음 40 억 개의 정수 목록을 다시 읽습니다. 그러나 이번에는 정수가 그 범위에있을 때만 주목할 것입니다. 당신이 그들을 찾을 때 조금 뒤집어.

del nums_in_bin # allow gc to free old 256kB array
from bitarray import bitarray
my_bit_array = bitarray(65536) # 32 kB
my_bit_array.setall(0)
for N in four_billion_int_array:
    if N // 65536 == bin_num:
        my_bit_array[N % 65536] = 1
for i, bit in enumerate(my_bit_array):
    if not bit:
        print bin_num*65536 + i
        break

3
정말 멋진 답변입니다. 이것은 실제로 작동합니다. 결과를 보장했습니다.
Jonathan Dickinson

@dr jimbob, 저장소에 하나의 숫자 만 있고 해당 단일 숫자에 65535 개의 중복이 있으면 어떻게됩니까? 그렇다면 빈은 여전히 ​​65536으로 계산되지만 모든 65536 숫자는 동일합니다.
Alcott

@Alcott-나는 당신이 2 ^ 32-1 (또는 더 적은) 숫자를 가지고 있다고 가정 했으므로, 비둘기 구멍 원리에 의해 당신은 더 자세한 내용을 확인하기 위해 65536보다 적은 수의 빈을 가지고 있다고 보장됩니다. 우리는 하나의 누락 된 정수만 찾으려고 노력하고 있습니다. 숫자가 2 ^ 32 이상인 경우 누락 된 정수를 보장 할 수 없으며이 방법을 사용할 수 없습니다 (또는 처음부터 누락 된 정수가 있음을 보증 함). 그러면 최선의 방법은 무차별 적입니다 (예를 들어, 배열을 32 번 읽고 처음 65536 번을 확인하고 일단 답을 찾으면 중지).
dr jimbob

영리한 upper-16 / lower-16 방법은 Henning : stackoverflow.com/a/7153822/224132에 의해 이전에 게시되었습니다 . 그래도 정확히 하나의 멤버가 누락 된 고유 한 정수 세트에 대한 추가 아이디어를 좋아했습니다.
Peter Cordes

3
@PeterCordes-네, Henning의 솔루션은 저보다 오래되었지만 제 답변이 여전히 유용하다고 생각합니다 (여러 가지 사항을 자세히 살펴보십시오). 즉, Jon Bentley는 그의 책 Programming Pearls에서 스택 오버 플로우가 존재하기 전에이 문제에 대한 다중 패스 옵션을 제안했습니다 (vine'th의 답변 참조) (내가 의식적으로 훔친다고 주장하거나 Bentley가 처음으로 이 문제를 분석하십시오-개발하는 것은 매우 자연스러운 해결책입니다). 제한이 더 이상 거대한 비트 배열의 1 패스 솔루션을위한 충분한 메모리를 가지고 있지 않다는 제한이있을 때 두 패스가 가장 자연스러운 것 같습니다.
짐 보브 박사

37

왜 그렇게 복잡하게 만드나요? 파일에없는 정수를 요청 하시겠습니까?

지정된 규칙에 따라 저장해야 할 유일한 것은 파일에서 지금까지 본 가장 큰 정수입니다. 전체 파일을 읽은 후에는 1보다 큰 숫자를 리턴하십시오.

규칙에 따라 정수의 크기 또는 알고리즘이 반환하는 숫자에 대한 제한이 없으므로 maxint 또는 기타를 칠 위험이 없습니다.


4
max int가 파일에 없다면, 이것은 가능합니다.
PearsonArtPhoto

13
규칙은 32 비트 또는 64 비트 또는 기타로 지정하지 않으므로 지정된 규칙에 따라 max int가 없습니다. 정수는 컴퓨터 용어가 아니라 양수 또는 음수를 식별하는 수학 용어입니다.
Pete

충분히 사실이지만 64 비트 숫자라고 생각할 수 없으며 누군가가 최대 int 숫자를 몰래 숨겨서 알고리즘을 혼동하지 않을 것이라고 생각할 수는 없습니다.
PearsonArtPhoto

24
프로그래밍 언어가 지정되지 않은 경우 컨텍스트에서 "max int"의 전체 개념이 유효하지 않습니다. 예를 들어 긴 정수에 대한 파이썬의 정의를 살펴보십시오. 무한합니다. 지붕이 없습니다. 언제든지 추가 할 수 있습니다. 정수에 허용되는 최대 값을 가진 언어로 구현되었다고 가정합니다.
Pete

32

이진 검색의 변형을 사용하여 매우 적은 공간에서 해결할 수 있습니다.

  1. 숫자의 허용 범위를 시작 0으로 4294967295.

  2. 중간 점을 계산하십시오.

  3. 중간 점 값보다 작거나 같은 숫자의 수를 세면서 파일을 반복합니다.

  4. 같은 숫자가 없으면 완료된 것입니다. 중간 점 번호가 답입니다.

  5. 그렇지 않으면 숫자가 가장 적은 범위를 선택하고이 새로운 범위로 2 단계부터 반복하십시오.

파일을 통해 최대 32 개의 선형 스캔이 필요하지만 범위와 카운트를 저장하는 데 몇 바이트의 메모리 만 사용합니다.

이것은 16k 대신 2 개의 빈을 사용한다는 점을 제외하면 본질적으로 Henning의 솔루션 과 동일합니다.


2
주어진 매개 변수에 대한 최적화를 시작하기 전에 시작했습니다.
hmakholm

@Henning : 멋지다. 시공간 트레이드 오프를 쉽게 조정할 수있는 알고리즘의 좋은 예입니다.
hammar

@ hammar, 그러나 두 번 이상 나타나는 숫자가 있다면 어떨까요?
Alcott

@Alcott : 그러면 알고리즘은 스파 저 빈 대신 밀도가 높은 빈을 선택하지만 비둘기 구멍 원칙에 따라 완전히 빈을 선택할 수는 없습니다. (두 카운트 중 작은 숫자는 항상 빈 범위보다 작습니다.)
Peter Cordes

27

편집 좋아, 이것은 파일의 정수가 정적 분포를 따르는 것으로 가정하기 때문에 생각하지 않았습니다. 분명히 그들은 필요하지 않지만 심지어는 이것을 시도해야합니다.


32 억 개의 32 비트 정수가 있습니다. 파일에 어떻게 분포되어 있는지는 모르지만 최악의 경우는 Shannon 엔트로피가 가장 높은 경우입니다. 이 경우 파일에서 하나의 정수가 발생하지 않을 확률은 다음과 같습니다.

((2³²-1) / 2³²) ⁴ ⁰⁰⁰ ⁰⁰⁰ ⁰⁰⁰ ≈ .4

Shannon 엔트로피가 낮을수록이 확률은 평균적으로 높아지지만, 최악의 경우에도 임의의 정수로 5 번 추측 한 후 90 %의 확률이 발생합니다. 의사 난수 생성기를 사용하여 이러한 숫자를 만들고 목록에 저장하십시오. 그런 다음 int 다음에 int를 읽고 모든 추측과 비교하십시오. 일치하는 항목이 있으면이 목록 항목을 제거하십시오. 모든 파일을 다 읽은 후에는 둘 이상의 추측이 남을 가능성이 있습니다. 그들 중 하나를 사용하십시오. 추측 할 수없는 드문 (최악의 경우에도 10 %) 이벤트에서 새로운 임의의 정수 세트를 얻습니다.

메모리 소비 : 수십 바이트, 복잡성 : O (n), 오버 헤드 : 대부분의 시간은 어쨌든 정수를 비교하지 않고 피할 수없는 하드 디스크 액세스에 소비되므로 무시할 수 있습니다.


정적 분포를 가정 하지 않은 실제 최악의 경우 는 모든 정수가 최대로 발생한다는 것입니다. 파일에서 모든 정수의 1-4000000000 / 2³² ≈ 6 % 만 발생하지 않기 때문입니다. 따라서 몇 가지 추측이 필요하지만 여전히 많은 양의 메모리 비용이 들지 않습니다.


5
다른 사람이 이것에 대해 생각한 것을 보게되어 기쁩니다. 그러나 왜 아래쪽에 있는가? 이것은 1- 패스 알고리즘입니다. 10MB는 2.5M 추측에 충분하며 93 % ^ 2.5M ≈ 10 ^ -79000은 두 번째 스캔이 필요한 무시할만한 기회입니다. 이진 검색의 오버 헤드로 인해 적은 수의 추측을 사용하면 더 빠릅니다! 이것은 시간과 공간 모두에서 최적입니다.
Potatoswatter

1
@ Potatoswatter : 바이너리 검색에 대해 언급했습니다. 5 개의 추측 만 사용하는 경우 오버 헤드가 발생하지 않지만 확실히 10 이상입니다. 2M 추측을 할 수도 있지만 해시 세트에 저장하여 검색을 위해 O (1)을 가져와야합니다.
leftaroundabout

1
@ Potatoswatter 벤 헤일리의 동등한 답변은 정상에 올랐다
Brian Gordon

1
나는이 접근법을 좋아하지만 메모리 절약 개선을 제안 할 것입니다 : N 비트의 인덱스 스토리지를 사용할 수 있고 일정한 스토리지가있는 경우 구성 가능한 가역적 인 32 비트 스크램블링 함수 (순열)를 정의하고 임의의 순열을 선택하고 모두 지우십시오 인덱싱 된 비트. 그런 다음 파일에서 각 숫자를 읽고 스크램블하고 결과가 N보다 작 으면 해당 비트를 설정하십시오. 파일 끝에 비트가 설정되어 있지 않으면 인덱스에서 스크램블 기능을 반대로하십시오. 64KB의 메모리를 사용하면 단일 패스에서 512,000 개 이상의 숫자를 효과적으로 테스트 할 수 있습니다.
supercat

2
물론,이 알고리즘 경우 최악의 경우는 사용중인 동일한 난수 생성기에 의해 숫자가 생성 된 경우입니다. 그렇지 않다고 보장 할 수 있다고 가정 할 때 가장 좋은 방법은 선형 합동 난수 생성기를 사용하여 목록을 생성하는 것입니다. 따라서 의사 난수 방식으로 숫자 공간을 통과하게됩니다. 즉, 어떻게 든 실패하면 노력을 복제하지 않고 전체 범위의 int를 커버 할 때까지 (갭을 발견 한) 숫자를 계속 생성 할 수 있습니다.
Dewi Morgan

25

[0, 2 ^ x -1] 범위에서 누락 된 하나의 정수가 있으면 모두 함께 xor하십시오. 예를 들면 다음과 같습니다.

>>> 0 ^ 1 ^ 3
2
>>> 0 ^ 1 ^ 2 ^ 3 ^ 4 ^ 6 ^ 7
5

(이것이 질문에 정확하게 대답하지는 않지만 매우 유사한 질문에 대한 좋은 대답입니다.)


1
네, 하나의 정수가 없을 때 작동0 ^ 1 ^ 3 ^ 4 ^ 6 ^ 7 하는 것을 증명하는 것은 쉽지만 둘 이상이 없으면 자주 실패합니다. 예를 들어, 0입니다. [ 2에서 x의 제곱 거듭 제곱에 대해 2 x를 쓰고 xor b에 대해 a ^ b를 쓰면 모든 k <2 x 의 xor 는 0입니다.-k ^ ~ k = (2 ^ x)- k <2 ^ (x-1)의 경우 1, j = k + 2 ** (x-2) 인 경우 k ^ ~ k ^ j ^ ~ j = 0-한 숫자를 제외한 모든 xor 값
James Waldby-jwpat7

2
ircmaxell의 답변에 대한 언급에서 언급했듯이 : 문제는 "하나의 숫자가 누락되었습니다"라고 말하지 않고 파일의 40 억 숫자에 포함되지 않은 숫자를 찾는다고 말합니다. 32 비트 정수를 가정하면 파일에서 약 3 억 개의 숫자가 누락 될 수 있습니다. 누락 된 숫자와 일치하는 숫자 xor의 가능성은 약 7 %에 불과합니다.
James Waldby-jwpat7

이것은 처음에 질문을 읽을 때 생각했던 대답이지만 면밀히 살펴보면 질문이 이것보다 더 모호하다고 생각합니다. 참고로, 이것은 내가 생각했던 질문입니다 : stackoverflow.com/questions/35185/…
Lee Netherton

18

그들은 값이 큰 세트의 일부가 아닌지를 절대적으로 효율적으로 결정할 수 있는 확률 블룸 필터에 대해 들어 보았을 지 모릅니다 (그러나 확률이 높은 세트의 멤버인지 결정할 수 있습니다).


4
가능한 값의 90 % 이상이 설정되어 있으면 블룸 필터가 아마도 비트 필드로 퇴화해야하므로 이미 많은 답변이 사용됩니다. 그렇지 않으면 쓸모없는 완전히 채워진 비트 열로 끝납니다.
Christopher Creutzig

@Christopher Bloom 필터에 대한 나의 이해는 100 %에 도달 할 때까지 채워진 비트 어레이를 얻지 못한다는 것입니다.
Paul

... 그렇지 않으면 허위 부정을받습니다.
Paul

@Paul 채워진 비트 배열은 오 탐지를 제공하여 허용됩니다. 이 경우 블룸 필터는 음수 인 솔루션이 거짓 긍정을 반환하는 경우로 퇴화 될 가능성이 높습니다.
ataylor

1
@Paul : 해시 함수의 수에 항목 수를 곱한 값이 필드의 길이만큼 길어지면 채워진 비트 어레이를 얻을 수 있습니다. 물론 예외적 인 경우이지만 확률은 매우 빠르게 상승합니다.
Christopher Creutzig

17

원래 질문의 현재 문구를 기반으로 가장 간단한 해결책은 다음과 같습니다.

파일에서 최대 값을 찾은 다음 1을 추가하십시오.


5
파일에 MAXINT가 포함되어 있으면 어떻게됩니까?
Petr Peller

@Petr Peller : BIGINT 라이브러리는 기본적으로 정수 크기에 대한 제한을 제거합니다.
oosterwal

2
@oosterwal,이 답변이 허용 된 경우 파일을 읽을 필요없이 – 가능한 한 큰 숫자로 인쇄하십시오.
Nakilon

1
@oosterwal, 임의의 거대한 숫자가 인쇄 할 수있는 최대 수이고 파일에 있으면이 작업을 해결할 수 없습니다.
Nakilon

3
@Nakilon : +1 포인트가 찍 힙니다. 파일의 총 자릿수를 파악하고 그 자릿수를 가진 숫자를 인쇄하는 것과 거의 같습니다.
oosterwal

14

를 사용하십시오 BitSet. 바이트 당 8로 BitSet에 압축 된 40 억 개의 정수 (최대 2 ^ 32 정수로 가정)는 2 ^ 32 / 2 ^ 3 = 2 ^ 29 = 약 0.5Gb입니다.

조금 더 자세하게 추가하려면 숫자를 읽을 때마다 BitSet에서 해당 비트를 설정하십시오. 그런 다음 BitSet을 전달하여 존재하지 않는 첫 번째 숫자를 찾으십시오. 실제로, 임의의 숫자를 반복적으로 선택하고 존재하는 경우 테스트하여이를 효과적으로 수행 할 수 있습니다.

실제로 BitSet.nextClearBit (0)은 설정되지 않은 첫 번째 비트를 알려줍니다.

BitSet API를 살펴보면 0..MAX_INT 만 지원하는 것으로 보이므로 + 've 숫자와 -'ve 숫자 각각에 대해 하나씩 2 개의 BitSet이 필요할 수 있지만 메모리 요구 사항은 변경되지 않습니다.


1
또는 BitSet... 을 사용하지 않으려면 비트 배열을 사용해보십시오. 같은 일을
;;

12

크기 제한이없는 경우 가장 빠른 방법은 파일 길이를 가져 와서 파일 길이 + 1의 임의의 자릿수 (또는 "11111 ..."s)를 생성하는 것입니다. 장점 : 파일을 읽을 필요가 없으며 메모리 사용을 거의 0으로 최소화 할 수 있습니다. 단점 : 수십억 자리를 인쇄합니다.

그러나 유일한 요소가 메모리 사용을 최소화하고 다른 중요한 것은 없다면 이것이 최적의 솔루션입니다. 심지어 "가장 최악의 규칙 남용"상을받을 수도 있습니다.


11

우리가 숫자의 범위가 항상 2 ^ n (2의 거듭 제곱)이라고 가정하면 배타적 또는 작동합니다 (다른 포스터에서 볼 수 있듯이). 이유까지는 증명해 보자.

이론

다음과 같은 0 기반 정수 범위가 주어집니다. 2^n하나의 요소가 누락 된 요소가 , 알려진 값을 xor-ing하여 누락 된 숫자를 산출하여 해당 요소를 찾을 수 있습니다.

증거

n = 2를 봅시다. n = 2의 경우, 4, 0, 1, 2, 3의 고유 한 정수를 나타낼 수 있습니다. 비트 패턴은 다음과 같습니다.

  • 0-00
  • 1-01
  • 2-10
  • 3 ~ 11

이제 살펴보면 각각의 비트가 정확히 두 번 설정됩니다. 따라서 짝수 번 설정되고 배타적 또는 숫자의 숫자는 0이됩니다. 하나의 숫자가 누락되면 배타적 숫자는 배타적 숫자가없는 배타적 숫자의 결과입니다. 따라서 누락 된 숫자와 결과 배타적 정렬 숫자는 정확히 같습니다. 2를 제거하면 결과 xor는10 (또는 2)가됩니다.

이제 n + 1을 봅시다. 하자의 각 비트에 설정 한 횟수 전화 n, x각 비트가 설정 횟수를 n+1 y. 의 값은 y동일 할 것이다 y = x * 2있기 때문에 x와 요소 n+10 비트 세트 및 x와 소자 n+1(1)에 비트를 설정하고 이후 2x항상 짝수가 될 것이며, n+1항상 각 비트 횟수로 설정 한 것이다.

따라서 n=2작동하고 n+1작동하므로 xor 메소드는의 모든 값에 대해 작동합니다 n>=2.

0 기반 범위의 알고리즘

이것은 매우 간단합니다. 2 * n 비트의 메모리를 사용하므로 <= 32 범위의 경우 2 32 비트 정수가 작동합니다 (파일 설명자가 소비하는 모든 메모리는 무시). 그리고 파일의 단일 패스를 만듭니다.

long supplied = 0;
long result = 0;
while (supplied = read_int_from_file()) {
    result = result ^ supplied;
}
return result;

임의의 범위에 대한 알고리즘

이 알고리즘은 총 범위가 2 ^ n 인 한 시작 숫자부터 끝 숫자까지의 범위에서 작동합니다. 기본적으로 범위를 0에서 최소값으로 재 지정합니다. 그러나 2 패스가 필요합니다. 파일을 통해 (첫 번째는 최소값을 잡고 두 번째는 누락 된 int를 계산합니다).

long supplied = 0;
long result = 0;
long offset = INT_MAX;
while (supplied = read_int_from_file()) {
    if (supplied < offset) {
        offset = supplied;
    }
}
reset_file_pointer();
while (supplied = read_int_from_file()) {
    result = result ^ (supplied - offset);
}
return result + offset;

임의의 범위

모든 범위가 2 ^ n의 거듭 제곱을 적어도 한 번 초과하므로이 수정 된 방법을 임의의 범위 세트에 적용 할 수 있습니다. 누락 된 단일 비트가있는 경우에만 작동합니다. 정렬되지 않은 파일은 2 패스가 필요하지만 매번 누락 된 단일 번호를 찾습니다.

long supplied = 0;
long result = 0;
long offset = INT_MAX;
long n = 0;
double temp;
while (supplied = read_int_from_file()) {
    if (supplied < offset) {
        offset = supplied;
    }
}
reset_file_pointer();
while (supplied = read_int_from_file()) {
    n++;
    result = result ^ (supplied - offset);
}
// We need to increment n one value so that we take care of the missing 
// int value
n++
while (n == 1 || 0 != (n & (n - 1))) {
    result = result ^ (n++);
}
return result + offset;

기본적으로 0을 기준으로 범위를 다시 조정합니다. 그런 다음 배타적 값을 계산할 때 추가 할 정렬되지 않은 값의 수를 셉니다. 그런 다음 정렬되지 않은 값의 수에 1을 더하여 결 측값을 처리합니다 (결 측값 계산). 그런 다음 n이 2의 거듭 제곱이 될 때까지 매번 1 씩 증가하여 n 값을 계속 xoring합니다. 결과는 원래 기준으로 다시 기반이됩니다. 끝난.

다음은 PHP에서 테스트 한 알고리즘입니다 (파일 대신 배열을 사용하지만 개념은 동일 함).

function find($array) {
    $offset = min($array);
    $n = 0;
    $result = 0;
    foreach ($array as $value) {
        $result = $result ^ ($value - $offset);
        $n++;
    }
    $n++; // This takes care of the missing value
    while ($n == 1 || 0 != ($n & ($n - 1))) {
        $result = $result ^ ($n++);
    }
    return $result + $offset;
}

누락 된 해당 범위 안에 하나가있는 모든 범위의 값 (음수 포함 테스트)이있는 배열에서 공급하면 매번 올바른 값을 찾았습니다.

또 다른 접근법

외부 정렬을 사용할 수 있으므로 차이를 확인하지 않는 이유는 무엇입니까? 이 알고리즘을 실행하기 전에 파일이 정렬되어 있다고 가정하면 :

long supplied = 0;
long last = read_int_from_file();
while (supplied = read_int_from_file()) {
    if (supplied != last + 1) {
        return last + 1;
    }
    last = supplied;
}
// The range is contiguous, so what do we do here?  Let's return last + 1:
return last + 1;

3
문제는 "하나의 숫자가 없습니다"라고 말하지 않고 파일의 40 억 숫자에 포함되지 않은 숫자를 찾는다는 것입니다. 32 비트 정수를 가정하면 파일에서 약 3 억 개의 숫자가 누락 될 수 있습니다. 누락 된 숫자와 일치하는 숫자 xor의 가능성은 약 7 %에 불과합니다.
James Waldby-jwpat7

연속적이지만 누락 된 범위가 0을 기준으로하지 않으면 xor 대신 추가하십시오. sum(0..n) = n*(n+1)/2. 그래서 missing = nmax*(nmax+1)/2 - nmin*(nmin+1)/2 - sum(input[]). (@hammar의 답변에서 온 아이디어)
Peter Cordes

9

부적절하게 인용되지 않는 한 속임수 질문. 최대 정수를 얻기 위해 파일을 한 번 읽고을 n반환하십시오 n+1.

물론 n+1정수 오버플로 가 발생할 경우 백업 계획이 필요합니다 .


3
작동하지 않는 경우를 제외하고 작동하는 솔루션이 있습니다. 유능한! :-)
dty

잘못 인용되지 않는 한, 질문은 정수 유형이나 사용되는 언어에 국한되지 않았습니다. 많은 현대 언어에는 사용 가능한 메모리로만 제한되는 정수가 있습니다. 파일에서 가장 큰 정수가 10MB보다 크면 운이 좋으며 두 번째 경우에는 작업이 불가능합니다. 내가 가장 좋아하는 솔루션.
위르겐 스트로벨

9

입력 파일의 크기를 확인한 다음 해당 파일로 표현하기에는 너무 큰 숫자 를 출력 하십시오 . 이것은 저렴한 트릭처럼 보이지만 인터뷰 문제에 대한 창의적인 해결책이며 메모리 문제를 깔끔하게 회피하며 기술적으로 O (n)입니다.

void maxNum(ulong filesize)
{
    ulong bitcount = filesize * 8; //number of bits in file

    for (ulong i = 0; i < bitcount; i++)
    {
        Console.Write(9);
    }
}

인쇄해야 10 bitcount 1 - 항상보다 큰 것, 2 bitcount을 . 기술적으로, 당신이 비트에있는 번호는 2 bitcount는 - (4 * 10 (9) - 1) , 당신은 (40 억 - 1)이 알고 있기 때문에 파일의 다른 정수, 심지어 완벽한 압축 그들이 적어도 걸릴거야 1 비트 씩


Console.Write( 1 << bitcount )루프 대신에? 존재하는 경우 해당 파일의 비트 다음 선두 1 임의 (_N_ + 1) 비트의 수가 절대적으로 큰 것이 보장된다.
Emmet

@Emmet-파일이 int (C #에서는 4 바이트)의 크기보다 작지 않으면 정수 오버플로가 발생합니다. C ++로 더 큰 것을 사용할 수는 있지만 C #에서는 <<연산자로 32 비트 정수를 허용하지 않는 것 같습니다 . 어느 쪽이든, 거대한 정수 유형을 굴리지 않으면 파일 크기가 매우 작아집니다. 데모 : rextester.com/BLETJ59067
Justin Morgan

8
  • 가장 간단한 방법은 파일에서 최소 숫자를 찾아 그보다 1을 적게 반환하는 것입니다. 이것은 O (1) 스토리지를 사용하고 n 숫자의 파일에 O (n) 시간을 사용합니다. 그러나 숫자 범위가 제한되어 있으면 최소 -1이 아닌 숫자가 될 수 있습니다.

  • 비트 맵을 사용하는 간단하고 간단한 방법은 이미 언급되었습니다. 이 방법은 O (n) 시간과 저장 공간을 사용합니다.

  • 2 ^ 16 카운팅 버킷을 가진 2- 패스 방법도 언급되었습니다. 2 * n 정수를 읽으므로 O (n) 시간과 O (1) 스토리지를 사용하지만 2 ^ 16 이상의 숫자를 가진 데이터 세트를 처리 할 수 ​​없습니다. 그러나 2 대신 4 패스를 실행하여 (예를 들어) 2 ^ 60 64 비트 정수로 쉽게 확장하고 메모리에 맞는 수의 빈 만 사용하고 이에 따라 패스 수를 늘려 작은 메모리를 사용하도록 쉽게 조정할 수 있습니다. 이 경우 런타임은 더 이상 O (n)가 아니라 대신 O (n * log n)입니다.

  • rfrankel이 지금까지 언급하고 ircmaxell이 길게 언급 한 모든 숫자를 함께 XOR하는 방법은 ltn100이 지적한 것처럼 stackoverflow # 35185 에서 묻는 질문에 대답합니다 . O (1) 스토리지와 O (n) 런타임을 사용합니다. 현재 32 비트 정수를 가정하면 XOR은 7 %의 확률로 고유 한 숫자를 생성 할 수 있습니다. 이론적 근거 : ~ 4G의 고유 한 숫자가 함께 XOR되고, ca. 파일에없는 300M의 경우, 각 비트 위치의 설정 비트 수가 홀수 또는 짝수 일 가능성이 동일합니다. 따라서 2 ^ 32 숫자는 XOR 결과로 발생할 가능성이 동일하며 그 중 93 %가 이미 파일에 있습니다. 파일의 숫자가 모두 고유하지 않으면 XOR 방법의 성공 확률이 높아집니다.


7

어떤 이유로,이 문제를 읽 자마자 나는 대각선 화를 생각했습니다. 임의로 큰 정수를 가정합니다.

첫 번째 숫자를 읽으십시오. 40 억 비트가 될 때까지 0 비트로 왼쪽 채 웁니다. 첫 번째 (고차) 비트가 0이면 출력 1; else output 0. (실제로 왼쪽 패드를 쓸 필요는 없습니다. 숫자에 충분한 비트가 없으면 1 만 출력하십시오.) 두 번째 비트를 사용하는 것을 제외하고 두 번째 숫자와 동일하게하십시오. 이 방법으로 파일을 계속 진행하십시오. 한 번에 한 비트 씩 40 억 비트 숫자를 출력 할 것이고 그 숫자는 파일의 숫자와 같지 않을 것입니다. 증명 : 그것은 n 번째 숫자와 같았으며, 그들은 n 번째 비트에 동의하지만 건축 적으로는 아닙니다.


창의력 +1 (싱글 패스 솔루션의 경우 최악의 출력)
hmakholm

그러나 대각선화할 40 억 비트가없고 32 개만 있습니다. 목록의 첫 32 개 숫자와 다른 32 비트 숫자로 끝납니다.
Brian Gordon

@Henning 그것은 거의 단일 패스가 아니다; 여전히 단항에서 이진으로 변환해야합니다. 편집 : 글쎄, 그것은 파일을 한 번 넘은 것 같아요. 신경 쓰지 마.
Brian Gordon

@ 브라이언, 여기에 "단항"이 있습니까? 대답은 한 번에 한 비트 씩 이진 응답을 구성하고 입력 파일을 한 번만 읽고 단일 패스로 만듭니다. ( 소수점 출력이 필요한 경우 문제가 발생합니다. 세 개의 입력 번호 당 하나의 10 진수를 구성하는 것이 더 좋으며 출력 번호의 로그를 10 % 증가시키는 것이 좋습니다.)
hmakholm

2
@Henning 많은 사람들이 지적했듯이 가장 큰 숫자를 찾아서 추가하거나 파일 자체에서 매우 긴 숫자를 구성하는 것은 사소한 일이기 때문에 문제는 임의의 큰 정수에는 의미가 없습니다. 이 대각선 화 솔루션은 ith 비트 에서 분기하는 대신 40 억 번 1 비트 만 출력하고 끝에 1을 더 던질 수 있기 때문에 특히 적합하지 않습니다 . 알고리즘에 임의로 큰 정수 가 있으면 문제가 없지만 누락 된 32 비트 정수를 출력하는 것이 문제라고 생각합니다. 다른 방법으로는 의미가 없습니다.
Brian Gordon

6

비트 플래그를 사용하여 정수 유무를 표시 할 수 있습니다.

전체 파일을 순회 한 후 각 비트를 스캔하여 숫자가 존재하는지 여부를 판별하십시오.

각 정수가 32 비트라고 가정하면 비트 플래그 지정이 완료되면 1GB의 RAM에 편리하게 맞습니다.


바이트를 4 비트로 재정의하지 않는 한
0.5Gb

2
@dty 나는 1Gb에 많은 공간이있을 것이기 때문에 "편안하게"를 의미한다고 생각한다.
corsiKa

6

파일에서 공백과 숫자가 아닌 문자를 제거하고 1을 추가하십시오. 이제 파일에는 원본 파일에 나열되지 않은 단일 숫자가 포함됩니다.

카본 등으로 Reddit에서.


그것을 사랑하십시오! 비록 그것이 답이 아니 었음에도 불구하고 ... : D
Johann du Toit

6

완벽을 기하기 위해 여기에 실행하는 데 오랜 시간이 걸리지 만 메모리는 거의 사용하지 않는 매우 간단한 또 ​​다른 솔루션이 있습니다.

가능한 모든 정수의 범위하자 int_minint_max하고, bool isNotInFile(integer)파일 (파일의 각 정수와 그 특정 정수를 비교하여) 특정 정수 거짓 다른 사람을 포함하지 않는 경우는 true를 반환하는 함수

for (integer i = int_min; i <= int_max; ++i)
{
    if (isNotInFile(i)) {
        return i;
    }
}

문제는 isNotInFile함수 알고리즘에 관한 것이 었습니다 . 답변하기 전에 질문을 이해했는지 확인하십시오.
Aleks G

2
아니오, 질문은 "파일에있는 정수 x"가 아니라 "파일에있는 정수 x"가 아닙니다. 후자의 질문에 대한 답을 결정하는 함수는 예를 들어 파일의 모든 정수를 해당 정수와 비교하고 일치하는 경우 true를 반환 할 수 있습니다.
deg

이것이 합법적 인 답변이라고 생각합니다. I / O를 제외하고 하나의 정수와 bool 플래그 만 필요합니다.
브라이언 고든

@ Aleks G-왜 이것이 잘못된 것으로 표시되는지 알 수 없습니다. 우리는 모두 :-) 중에서 가장 느린 알고리즘이라는 데 동의하지만 파일을 읽으려면 4 바이트가 필요합니다. 원래 질문은 파일을 규정하지 않지만 예를 들어 한 번만 읽을 수 있습니다.
Simon Mourier

1
@Aleks G-맞습니다. 당신도 그렇게 말한 적이 없어요 우리는 파일에 루프를 사용하여 IsNotInFile을 간단하게 구현할 수 있다고 말합니다. Open; 4 바이트의 메모리 만 필요합니다.
Simon Mourier

5

10MB 메모리 제한의 경우 :

  1. 숫자를 이진 표현으로 변환하십시오.
  2. left = 0 및 right = 1 인 이진 트리를 만듭니다.
  3. 이진 표현을 사용하여 트리에 각 숫자를 삽입하십시오.
  4. 숫자가 이미 삽입 된 경우 리프가 이미 생성 된 것입니다.

완료되면 요청 된 번호를 작성하기 전에 작성되지 않은 경로를 사용하십시오.

40 억 숫자 = 2 ^ 32, 즉 10MB로는 충분하지 않을 수 있습니다.

편집하다

두 개의 끝 잎이 만들어지고 공통 부모가있는 경우 최적화가 가능하며, 제거하고 부모가 솔루션이 아닌 것으로 플래그 될 수 있습니다. 이것은 가지를 줄이고 메모리의 필요성을 줄입니다.

편집 II

트리를 완전히 만들 필요는 없습니다. 숫자가 비슷한 경우에만 깊은 가지를 만들면됩니다. 우리도 가지를 자르면이 솔루션이 실제로 작동 할 수 있습니다.


6
... 10MB에 어떻게 맞습니까?
hmakholm

어떻습니까 : BTree의 깊이를 10MB에 맞는 것으로 제한하십시오. 이것은 {false positive | 긍정적 인} 그리고 당신은 그것을 반복하고 다른 기술을 사용하여 가치를 찾을 수 있습니다.
Jonathan Dickinson

5

1GB 버전에 답하겠습니다.

질문에 충분한 정보가 없으므로 먼저 몇 가지 가정을 설명하겠습니다.

정수는 -2,147,483,648에서 2,147,483,647 범위의 32 비트입니다.

의사 코드 :

var bitArray = new bit[4294967296];  // 0.5 GB, initialized to all 0s.

foreach (var number in file) {
    bitArray[number + 2147483648] = 1;   // Shift all numbers so they start at 0.
}

for (var i = 0; i < 4294967296; i++) {
    if (bitArray[i] == 0) {
        return i - 2147483648;
    }
}

4

창의적인 답변을하는 한 여기 또 다른 답변이 있습니다.

외부 정렬 프로그램을 사용하여 입력 파일을 숫자로 정렬하십시오. 이것은 필요한 메모리 양에 관계없이 작동합니다 (필요한 경우 파일 스토리지를 사용함). 정렬 된 파일을 읽고 누락 된 첫 번째 숫자를 출력하십시오.


3

비트 제거

한 가지 방법은 비트를 제거하는 것이지만 실제로 결과를 얻지 못할 수도 있습니다 (그렇지 않을 수도 있습니다). 슈도 코드 :

long val = 0xFFFFFFFFFFFFFFFF; // (all bits set)
foreach long fileVal in file
{
    val = val & ~fileVal;
    if (val == 0) error;
}

비트 수

비트 수를 추적하십시오. 가장 적은 양의 비트를 사용하여 값을 생성합니다. 다시 말하지만 이것은 올바른 값을 생성한다고 보장하지 않습니다.

레인지 로직

목록 순서 범위를 추적하십시오 (시작 순서로 정렬). 범위는 다음 구조로 정의됩니다.

struct Range
{
  long Start, End; // Inclusive.
}
Range startRange = new Range { Start = 0x0, End = 0xFFFFFFFFFFFFFFFF };

파일의 각 값을 살펴보고 현재 범위에서 제거하십시오. 이 방법은 메모리를 보장하지는 않지만 꽤 잘 작동합니다.


3

2 128 * 10 18 + 1 (인 (2 8 ) 16 * 10 18 + 1) - 오늘날위한 범용 대답 할 수 없는가? 16 EB 파일에 보유 할 수없는 숫자를 나타내며 현재 파일 시스템의 최대 파일 크기입니다.


그리고 결과를 어떻게 인쇄하겠습니까? 파일에 넣을 수 없으며 화면에 인쇄하는 데 몇 십억 년이 걸립니다. 오늘날의 컴퓨터에서 가동 시간이 달성되지는 않습니다.
vsz

우리가 결과를 어디에서나 인쇄해야한다고 말하지 않고 단지 '생성'하면됩니다. 따라서 생성의 의미에 따라 다릅니다. 어쨌든, 내 대답은 실제 알고리즘 : 운동을 피하기 위해 단지 속임수
마이클 Sagalovich

3

나는 이것이 해결 된 문제라고 생각하지만 (위 참조) 질문이있을 수 있으므로 염두에 두어야 할 흥미로운 사례가 있습니다.

반복없이 정확히 4,294,967,295 (2 ^ 32-1) 32 비트 정수가 있으므로 하나만 누락되면 간단한 해결책이 있습니다.

누계를 0에서 시작하고 파일의 각 정수에 대해 32 비트 오버플로가있는 정수를 추가합니다 (실제 runningTotal = (runningTotal + nextInteger) % 4294967296). 완료되면 3229 오버플로로 누적 합계에 4294967296/2를 추가하십시오. 이것을 4294967296에서 빼면 결과는 누락 된 정수입니다.

"단 하나의 누락 된 정수"문제는 한 번의 실행과 64 비트의 RAM 만 데이터 전용으로 해결할 수 있습니다 (실행중인 총 32, 다음 정수에서 32).

Corollary : 정수 결과에 몇 비트가 필요한지 염려하지 않으면보다 일반적인 사양은 매우 간단합니다. 우리는 주어진 파일에 포함 할 수없는 충분히 큰 정수를 생성합니다. 다시 말하지만 이것은 최소한의 RAM을 차지합니다. 의사 코드를 참조하십시오.

# Grab the file size
fseek(fp, 0L, SEEK_END);
sz = ftell(fp);
# Print a '2' for every bit of the file.
for (c=0; c<sz; c++) {
  for (b=0; b<4; b++) {
    print "2";
  }
}

@Nakilon과 TheDayTurns는 원래 질문에 대한 의견에서 이것을 지적했습니다
Brian Gordon

3

Ryan이 기본적으로 말했듯이 파일을 정렬 한 다음 정수를 건너 뛰고 값을 건너 뛰면 거기에 있습니다 :)

downvoters에서 편집 : OP는 파일을 정렬 할 수 있다고 말했으며 이것이 유효한 방법입니다.


한 가지 중요한 부분은 당신이 갈 때 한 번만 읽어야한다는 것입니다. 실제 메모리 액세스 속도가 느립니다.
Ryan Amos

@ryan 외부 정렬은 대부분의 경우 병합 정렬이므로 마지막 병합에서 확인을 수행 할 수 있습니다 :)
ratchet freak

데이터가 디스크에 있으면 메모리에로드해야합니다. 이것은 파일 시스템에 의해 자동으로 발생합니다. 하나의 숫자를 찾아야하는 경우 (문제가 달리 명시하지 않음) 정렬 된 파일을 한 번에 한 줄씩 읽는 것이 가장 효율적인 방법입니다. 메모리를 거의 사용하지 않으며 다른 것보다 느리지 않습니다. 파일을 읽어야합니다.
Tony Ennis

1GB의 메모리 만 있으면 40 억 개의 정수를 어떻게 정렬합니까? 가상 메모리를 사용하는 경우 메모리 블록이 실제 메모리에서 페이징되거나 페이징되지 않으므로 시간이 오래 걸립니다.
클라스 Lindbäck

4
@klas merge sort 는이를 위해 설계되었습니다.
ratchet freak

2

32 비트 제약 조건을 가정하지 않으면 임의로 생성 된 64 비트 숫자 (또는 비관적 인 경우 128 비트)를 반환하십시오. 충돌 가능성은 1 in 2^64/(4*10^9) = 4611686018.4약 40 억분의 1입니다. 당신은 대부분의 시간에 맞을 것입니다!

(농담 ... 종류)


나는 이것이 이미 제안 된 것을 본다 :) 그 사람들을위한 찬사
Peter Gibson

생일 역설은 무작위 추측이 실제로 유효한 대답인지 확인하기 위해 파일을 확인하지 않고 이러한 종류의 솔루션을 위험 가치가 없도록 만듭니다. (이 경우 생일 역설은 적용되지 않지만 새로운 고유 값을 생성하기 위해이 함수를 반복해서 호출하면 생일 역설 상황이 발생합니다.)
Peter Cordes

그들은 심지어 위키 피 디아에서 충돌의 확률 계산 생일 역설 언급 - 무작위로 128 비트 번호를 생성 @PeterCordes 방법 UUID를 작업 정확하게 UUID 페이지
피터 깁슨

변형 : 세트에서 최대 값을 찾고 1을 추가합니다.
Phil

원래 배열 (추가 저장 공간 없음)을 신속하게 정렬 한 다음 배열을 통과하여 첫 번째 '스킵 된'정수를보고합니다. 끝난. 질문에 대답했습니다.
레벨 42
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.