목록에없는 가장 작은 정수 찾기


87

제 동료가 사용하는 흥미로운 인터뷰 질문 :

부호없는 64 비트 정수의 매우 길고 정렬되지 않은 목록이 제공되었다고 가정하십시오. 목록에 없는 가장 작은 음이 아닌 정수를 어떻게 찾을 수 있습니까?

FOLLOW-UP : 이제 분류에 의한 명백한 해결책이 제안되었으므로 O (n log n)보다 빠르게 할 수 있습니까?

후속 조치 : 알고리즘은 1GB 메모리가있는 컴퓨터에서 실행되어야합니다.

설명 :이 목록은 많은 양을 소비 할 수 있지만 RAM에 있습니다. 목록의 크기 (예 : N)가 미리 제공됩니다.


6
부호없는 정수에 대해 어떻게 말하는지 확인하면서 음이 아닌 부분을 생략 할 수 있다고 생각합니다.
KevenDenen

4
내가 IMO를 벗어난 경우가 아니라면 질문은 매우 기본적이지만 다른 사람들이 언급했듯이 질문해야 할 질문이나 진술해야 할 가정이 있습니다.
James Black

8
@paxdiablo : 이것은 O (n)이라고 말하는 것이 그다지 의미가없는 경우입니다. 2 ^ 64 비트 어레이를 Easter Island의 점토 태블릿에 저장하고 캐리어 비둘기로 액세스하더라도 알고리즘은 여전히 ​​O (n)입니다.
IJ Kennedy

6
;-) 좋은 인터뷰 질문이 차종을 통해 중간 메모리 요구 사항을 변경
크리스 밸런스

1
모든 답변이 동일한 일반 솔루션 (배열을 정렬하고 시퀀스를 깨는 첫 번째 값을 찾습니다)을 수행하는 것이 재미 있다고 생각하지만 모두 다른 정렬을 사용합니다. (수정 된 퀵 정렬, 기수 정렬, ...) 허용되는 답변은 N 위의 요소를 버리는 계수 정렬과 동일합니다.
Joren

답변:


121

데이터 구조가 제자리에서 변경 될 수 있고 임의 액세스를 지원하는 경우 O (N) 시간 및 O (1) 추가 공간에서 수행 할 수 있습니다. 배열을 순차적으로 살펴보고 모든 인덱스에 대해 인덱스의 값을 값으로 지정된 인덱스에 쓰고 해당 위치의 값을 해당 위치에 재귀 적으로 배치하고 값> N을 버립니다. 그런 다음 다시 배열을 통해 스팟을 찾습니다. 값이 인덱스와 일치하지 않는 경우-배열에없는 가장 작은 값입니다. 이로 인해 최대 3N 비교가 수행되고 임시 공간에 해당하는 몇 가지 값만 사용됩니다.

# Pass 1, move every value to the position of its value
for cursor in range(N):
    target = array[cursor]
    while target < N and target != array[target]:
        new_target = array[target]
        array[target] = target
        target = new_target

# Pass 2, find first location where the index doesn't match the value
for cursor in range(N):
    if array[cursor] != cursor:
        return cursor
return N

9
작은 nitpick. 목록이 {0, ..., N-1} 인 사소한 경우를 놓쳤습니다. 이 경우 패스 1은 아무 작업도 수행하지 않고 패스 2에서는 목록의 모든 항목에 대해 array [cursor] == 커서이므로 알고리즘이 반환되지 않습니다. 따라서 끝에 'return N'문이 필요합니다.
Alex

12
솔루션은 도메인과 범위를 병합합니다 (목표는 값이자 인덱스입니다). 범위는 사용 가능한 스토리지에 따라 128M 요소로 제한되지만 도메인 크기는 2G입니다. 배열에 할당 할 수있는 항목 수보다 큰 값을 가진 단일 항목으로 실패합니다. 질문이 '매우 긴'을 지정하지 않은 경우 입력을 파괴하더라도 대답은 우아합니다. 이 문제에서 시공간 절충은 매우 명백하며 제공된 제약 조건 하에서 O (N) 솔루션이 불가능할 수 있습니다.
Pekka 2013 년

2
두 번째 패스는 선형 검색 대신 이진 검색을 사용할 수 있습니다.
user448810 2013

4
이 솔루션은 값과 인덱스의 범위가 비슷한 경우에만 작동합니다.
Dubby

7
더 큰 값으로 잘 작동합니다. 더 큰 값은 배열에없는 가장 작은 값과 관련이 없기 때문에 무시할 수 있습니다. 예를 들어 첫 번째 패스는 대상 <N으로 인해 모든 값을 무시하고 배열을 반복하고 두 번째 패스의 첫 번째 반복에서 0을 반환합니다.
Ants Aasma

89

다음 O(N)O(N)공간 을 사용 하는 간단한 솔루션입니다 . 입력 목록을 음수가 아닌 숫자로 제한하고 목록에없는 첫 번째 음이 아닌 숫자를 찾고 싶다고 가정합니다.

  1. 목록의 길이를 찾으십시오. 그것은이라고 말할 수 있습니다 N.
  2. Nall로 초기화 된 부울 배열을 할당 합니다 false.
  3. X목록의 각 숫자 에 대해 X보다 작은 경우 배열 NX'th요소를로 설정합니다 true.
  4. index 0에서 시작하는 배열을 스캔하여 인 첫 번째 요소를 찾습니다 false. 첫 번째 발견하면 false인덱스를 I, 다음 I답변입니다. 그렇지 않으면 (즉, 모든 요소가있을 때 true) 대답은 N입니다.

실제로 " N부울 배열 "은 byte또는 int배열 로 표시되는 "비트 맵"또는 "비트 세트"로 인코딩 될 수 있습니다 . 이것은 일반적으로 더 적은 공간 (프로그래밍 언어에 따라 다름)을 사용하고 첫 번째 스캔이 false더 빨리 수행되도록합니다.


이것이 알고리즘이 작동하는 방법 / 이유입니다.

N목록 의 숫자가 구별되지 않거나 그 중 하나 이상이보다 크다고 가정 N합니다. 이는 목록에없는 범위에 숫자가 하나 이상 있어야 함을 의미 0 .. N - 1합니다. 따라서 가장 작은 결측 수를 찾는 문제는 따라서 보다N 작은 결측 수를 찾는 문제로 축소되어야합니다 . 이것은 N... 보다 크거나 같은 숫자를 추적 할 필요가 없다는 것을 의미합니다 . 왜냐하면 그들은 답이 될 수 없기 때문입니다.

이전 단락의 대안은 목록이에서 번호의 순열이라는 것입니다 0 .. N - 1. 이 경우 3 단계에서는 배열의 모든 요소를로 설정하고 true4 단계에서는 첫 번째 "누락 된"숫자가 N입니다.


알고리즘의 계산 복잡성은 O(N)상대적으로 작은 비례 상수를 갖습니다. 목록을 두 번의 선형 전달하거나 목록 길이가 시작하는 것으로 알려진 경우 한 번만 전달합니다. 전체 목록을 메모리에 저장할 필요가 없습니다. 따라서 알고리즘의 점근 적 메모리 사용량은 부울 배열을 나타내는 데 필요한 것입니다. 즉 O(N)비트.

(반대로, 메모리 내 정렬 또는 분할에 의존하는 알고리즘은 전체 목록을 메모리에 표시 할 수 있다고 가정합니다. 질문 형식에서는 O(N)64 비트 단어 가 필요합니다 .)


1 ~ 3 단계의 @Jorn 주석은 계산 정렬의 변형입니다. 어떤 의미에서 그는 옳지 만 차이점은 중요합니다.

  • 계수 정렬에는 목록에서 가장 큰 숫자이고 목록 에서 가장 작은 숫자 인 (적어도) Xmax - Xmin카운터 배열이 필요 합니다. 각 카운터는 N 개의 상태를 나타낼 수 있어야합니다. 즉, 이진 표현을 가정하면 정수 유형 (적어도) 비트 가 있어야 합니다.XmaxXminceiling(log2(N))
  • 배열 크기를 결정하려면 계수 정렬을 통해 목록을 처음으로 통과하여 Xmax및 을 결정해야합니다 Xmin.
  • 따라서 최소 최악의 경우 공간 요구 사항은 ceiling(log2(N)) * (Xmax - Xmin)비트입니다.

대조적으로, 위에 제시된 알고리즘은 단순히 N최악의 경우와 최상의 경우의 비트를 필요로합니다 .

그러나이 분석은 알고리즘이 목록을 처음으로 통과하여 0을 찾고 (필요한 경우 목록 요소를 세는 경우) 0을 찾으면 공백을 사용하지 않고 더 빠른 응답을 제공한다는 직관으로 이어집니다. 목록에서 하나 이상의 0을 찾을 가능성이 높은 경우이 작업을 수행 할 가치가 있습니다. 그리고이 추가 패스는 전반적인 복잡성을 변경하지 않습니다.


편집 : 사람들이 비트와 비트 맵을 사용하는 내 원래 설명이 혼란스러워 보이기 때문에 "부울 배열"을 사용하도록 알고리즘 설명을 변경했습니다.


3
@ adi92 3 단계에서 모든 비트가 1로 설정된 비트 맵을 제공하면 목록에 0에서 N-1까지의 모든 값이 포함됩니다. 즉, 목록에서 음이 아닌 가장 작은 정수는 N입니다. 목록에없는 0과 N-1 사이의 값이 있으면 해당 비트가 설정되지 않습니다. 따라서 그러한 가장 작은 값이 답입니다.
divegeek

4
@ adi92 예제에서 목록에는 300 개의 요소가 포함됩니다. 즉, "누락 된"값이 있으면 300보다 작아야합니다. 알고리즘을 실행하면 300 개의 슬롯이있는 비트 필드를 만든 다음 슬롯 1, 2, 3에 비트를 반복적으로 설정하고 모든 다른 슬롯 (0 및 4 ~ 299)은 지워집니다. 비트 필드를 스캔 할 때 슬롯 0의 플래그가 지워졌으므로 0이 답이라는 것을 알 수 있습니다.
divegeek

4
이 알고리즘은 "크기 N의 부울 배열 만들기"등과 같이 비트 뒤틀림없이 더 간단하게 이해할 수 있습니다. 그런 식으로 이해하면 비트 버전으로 이동하는 것이 개념적으로 쉽습니다.
Jon Skeet

2
추상적 인 솔루션을 제공 할 때는 개념적으로 가장 간단한 작동 방식을 사용하고 지나치게 전문화하지 마십시오. 귀하의 솔루션은 (추상) 부울 배열 사용에 비명을 지르므로 그렇게 부르십시오. 이 배열을 bool[]비트 맵 으로 또는 비트 맵으로 구현할 수 있다는 것은 일반적인 솔루션과 관련이 없습니다.
Joren

2
이 솔루션은 "N 이상의 요소를 무시하는 계산 정렬을 사용한 다음 처음부터 선형 검색을 수행하여 첫 번째 누락 된 요소를 찾습니다."로 가장 잘 설명 될 수 있다고 생각합니다.
Joren

13

OP는 이제 원래 목록이 RAM에 있고 컴퓨터에 1GB의 메모리 만 있다고 지정 했으므로 팔다리로 나가 대답이 0이라고 예측할 것입니다.

1GB RAM은 목록에 최대 134,217,728 개의 숫자를 포함 할 수 있음을 의미합니다. 그러나 2 64 = 18,446,744,073,709,551,616 개의 가능한 숫자가 있습니다. 따라서 목록에 0이있을 확률은 137,438,953,472 중 1입니다.

대조적으로, 올해 번개맞을 확률은 70 만분의 1입니다. 운석에 맞을 확률 은 약 10 조분의 1입니다. 그래서 저는 답이 0이 아닌 것보다 천체에 의해 제때에 죽지 않아서 과학 저널에 기록 될 가능성이 약 10 배 더 높습니다.


11
값이 균일하게 분포되고 무작위로 선택된 경우에만 계산이 유지됩니다. 순차적으로 생성 될 수도 있습니다.
divegeek

1
물론 맞습니다. 하지만 저는 일반적인 경우를 최적화하는 것입니다. :)
Barry Brown

10
그렇다면이 답변으로 인터뷰 대상자가 선택 될 확률은 얼마입니까?
Amarghosh

6
질문은 숫자가 무작위로 균일하게 선택된다는 것을 말하지 않습니다. 이 질문을 설정하는 사람이 선택합니다. 이 점을 감안할 때 0이 목록에있을 확률은 137,438,953,472의 1보다 훨씬 큽니다. 아마도 2의 1보다 더 큽니다. :-)
ShreevatsaR

8
@Amarghosh 그 질문에 대한 답도 0입니다.
PeterAllenWebb

10

다른 답변에서 지적했듯이 정렬을 수행 한 다음 간격을 찾을 때까지 간단히 스캔 할 수 있습니다.

간격을 포함 할 수있는 잠재적 후보가 아닌 파티션을 제거하는 수정 된 QuickSort를 사용하여 알고리즘 복잡성을 O (N)으로 개선하고 O (N) 공간을 유지할 수 있습니다.

  • 첫 번째 파티션 단계에서 중복을 제거합니다.
  • 분할이 완료되면 하위 파티션의 항목 수를 확인합니다.
  • 이 값이 파티션 생성에 사용 된 값과 같습니까?
    • 그렇다면 더 높은 파티션에 간격이 있음을 의미합니다.
      • 하위 파티션을 무시하고 빠른 정렬을 계속합니다.
    • 그렇지 않으면 간격이 아래쪽 파티션에 있습니다.
      • 상위 파티션을 무시하고 빠른 정렬을 계속합니다.

이것은 많은 계산을 저장합니다.


꽤 멋져요. 선형 시간보다 짧은 시간에 파티션의 길이를 계산할 수 있다고 가정합니다. 이는 파티션 배열과 함께 저장되는 경우 수행 할 수 있습니다. 또한 원래 목록이 RAM에 있다고 가정합니다.
Barry Brown

2
목록의 길이를 알고 있다면 len (list)보다 큰 값을 추출 할 수도 있습니다. pigeonhole 원칙에 따라 모든 '구멍'은 len (list)보다 작아야합니다.
divegeek

1
나는 그것이 O (n)이라고 생각하지 않는다 ... 하나의 경우, 목록이 완전히 정렬 될 때까지 중복을 제거 할 수 있는지 확실하지 않습니다. (당신이 중간에서 이상으로 구분했기 때문에) 당신은 검색 공간 멀리 절반의 각 반복을 던지는을 보장 할 수있는 반면 둘째, 당신은 여전히 N에 따라 데이터 이상 (N에 따라 다름) 다중 패스가 있습니다.
paxdiablo

1
paxdiablo : Stephen C가 제안한 것과 같은 비트 맵 방법을 사용하여 고유 한 값으로 만 새 목록을 만들 수 있습니다. 이것은 O (n) 시간과 공간에서 실행됩니다. 그것보다 더 잘할 수 있을지 모르겠습니다.
Nic

9

O(N)사고 의 함정 중 하나를 설명하기 O(N)위해 O(1)공간 을 사용 하는 알고리즘이 있습니다.

for i in [0..2^64):
  if i not in list: return i

print "no 64-bit integers are missing"

1
윌이 옳다. 실제로 여기에 두 개의 루프가 있기 때문에 이것은 O (n)이 아니지만 하나는 암시 적입니다. 값이 목록에 있는지 확인하는 것은 O (n) 연산이고 for 루프에서 n 번 수행합니다. 그것은 O (n ^ 2)가됩니다.
Nic

6
Nic, Will, O (n * N)입니다. 여기서 n은 목록의 크기이고 N은 도메인의 크기 (64 비트 정수)입니다. N은 거대한 숫자이지만 여전히 상수이므로 공식적으로 언급 된 문제의 복잡성은 O (n)입니다.
Ants Aasma

1
개미, 나는 그것이 O (n N) 라는 것에 동의 하지만 N은 일정하지 않습니다. 알고리즘이 답을 찾으면 완료되기 때문에 외부 루프를 통한 완전한 반복 횟수는 답과 동일하며, 그 자체는 목록의 크기에 의해 제한됩니다. 따라서이 경우 O (N n)은 O (n ^ 2)입니다.
Will Harris

12
N 요소 목록에서 숫자를 찾는 것은 분명히 O (N)입니다. 우리는 이것을 2 ^ 64 번합니다. 크지 만 2 ^ 64는 CONSTANT입니다. 따라서 알고리즘은 여전히 ​​O (N) 인 C * O (N)입니다.
IJ Kennedy

3
나는 이전 진술을 철회해야합니다. 가장 엄격한 정의에 따르면이 연산은 실제로 O (n)입니다.
Nic

8

숫자는 모두 64 비트이므로 기수 정렬 (O (n))을 사용할 수 있습니다 . 정렬 한 다음 원하는 것을 찾을 때까지 스캔하십시오.

가장 작은 숫자가 0이면 간격을 찾을 때까지 앞으로 스캔하십시오. 가장 작은 숫자가 0이 아니면 답은 0입니다.


사실이지만 기수 정렬의 경우 메모리 요구 사항이 상당히 강해질 수 있습니다.
PeterAllenWebb

1
기수 정렬은 매우 큰 데이터 세트에서 작동하지 않습니다. 그러나 파티션 및 기수 정렬이 작동 할 수 있습니다.
DarthVader

5

공간 효율적인 방법과 모든 값이 구별되는 경우 공간 O( k )과 시간 에서 수행 할 수 있습니다 O( k*log(N)*N ). 공간 효율적이고 데이터 이동이 없으며 모든 작업은 기본 (더하기 빼기)입니다.

  1. 세트 U = N; L=0
  2. 먼저 k영역 의 숫자 공간을 분할합니다 . 이렇게 :
    • 0->(1/k)*(U-L) + L, 0->(2/k)*(U-L) + L , 0->(3/k)*(U-L) + L...0->(U-L) + L
  3. count{i}각 지역에 몇 개의 숫자 ( )가 있는지 확인합니다 . ( N*k단계)
  4. 첫 번째 지역 찾기 (h가득 차지 않은 )을 . 즉 count{h} < upper_limit{h}. ( k단계)
  5. h - count{h-1} = 1답이 있다면
  6. 세트 U = count{h}; L = count{h-1}
  7. 2로 이동

이것은 해싱을 사용하여 개선 할 수 있습니다 (Nic이 아이디어에 감사드립니다).

  1. 같은
  2. 먼저 k영역 의 숫자 공간을 분할합니다 . 이렇게 :
    • L + (i/k)->L + (i+1/k)*(U-L)
  3. inc count{j} 사용 j = (number - L)/k (if L < number < U)
  4. 첫 번째 지역 찾기 (hk 요소가없는 )
  5. 만약 count{h} = 1h가 당신의 대답
  6. 세트 U = maximum value in region h L = minimum value in region h

이것은에서 실행됩니다 O(log(N)*N).


이 답변이 정말 마음에 듭니다. 읽기가 조금 어려웠지만 질문을 읽을 때 머릿속에 있던 것과 매우 비슷합니다.
Nic

또한 어느 시점에서 Stephen C.의 비트 맵 솔루션으로 전환하는 것이 현명 할 것입니다. 아마도 언제U-L < k
Egon

이것은 O (log (N) * N)에서 실행되지 않고 O (N)에서 실행됩니다. 귀하의 답변은 @cdiggins 답변의 일반화이며, sum (1 / k ** i for i in range (ceil (log_k (n)))) <= 2이기 때문에 O (N)에서 실행됩니다.
Lapinot

각 반복에서 O (N) 숫자를 거치고 O (log_k (N)) 총 반복이 필요합니다. 따라서 O (log_k (N) * N) == O (log (N) * N). 원래 번호는 정렬 / 버킷되지 않으며 모든 번호를 살펴 봐야합니다.
Egon

그러나 원래 목록을 k 영역 (크기 n / k)으로 분할 한 경우 가득 차지 않은 첫 번째 영역을 선택합니다. 따라서 다음 반복에서는 선택한 영역을 고려하고 k 개의 새 영역 (크기 n / k ** 2) 등으로 나누면됩니다. 실제로 매번 전체 목록을 반복하지 않습니다 (그렇지 않으면 분할 지점은 ?).
Lapinot

3

나는 그것들을 정렬 한 다음 간격 (0과 첫 번째 숫자 사이의 간격 포함)을 찾을 때까지 시퀀스를 실행합니다.

알고리즘 측면에서 다음과 같이 할 수 있습니다.

def smallest_not_in_list(list):
    sort(list)
    if list[0] != 0:
        return 0
    for i = 1 to list.last:
        if list[i] != list[i-1] + 1:
            return list[i-1] + 1
    if list[list.last] == 2^64 - 1:
        assert ("No gaps")
    return list[list.last] + 1

물론 CPU grunt보다 더 많은 메모리가 있다면 가능한 모든 64 비트 값의 비트 마스크를 만들고 목록의 모든 숫자에 대해 비트를 설정할 수 있습니다. 그런 다음 해당 비트 마스크에서 첫 번째 0 비트를 찾습니다. 그것은 시간 측면에서 O (n) 작업으로 바뀌지 만 메모리 요구 사항 측면에서는 상당히 비쌉니다. :-)

나는 당신이 O (n)을 향상시킬 수 있을지 의심 스럽습니다. 적어도 한 번은 각 숫자를 보지 않는 방법을 볼 수 없기 때문입니다.

그 알고리즘은 다음과 같습니다.

def smallest_not_in_list(list):
    bitmask = mask_make(2^64) // might take a while :-)
    mask_clear_all (bitmask)
    for i = 1 to list.last:
        mask_set (bitmask, list[i])
    for i = 0 to 2^64 - 1:
        if mask_is_clear (bitmask, i):
            return i
    assert ("No gaps")

설명에서 그것은 목록에없는 가장 작은 요소이기 때문에 첫 번째 요소에 0을 배제하는 것처럼 보입니다. 그러나 그것은 내가 만든 가정이며, 나는 틀릴 수 있습니다.
James Black

내 생각에는 정렬 된 시퀀스가 ​​4,5,6이면 0이 목록에없는 가장 작은 것입니다.
paxdiablo

나는 2, 3, 5, 대답은 4가 될 것이라고 예상하지만, 나는 틀릴 수 있습니다.
James Black

OP에서 답변해야하는 질문입니다. 검색 공간이 "모든 64 비트 부호없는 정수"입니까 아니면 "목록에서 가장 낮은 값과 가장 높은 값 사이의 모든 숫자"입니까?
paxdiablo

나는 최악의 경우에 아마도 바이너리 트리에서 이미 정렬되지 않았다면 적어도 한 번은 봐야한다는 데 동의합니다.
James Black

2

목록을 정렬하고 첫 번째와 두 번째 요소를 살펴본 다음 간격이 생길 때까지 위로 올라가십시오.


정의하는 방법에 따라 목록에 없습니다.
James Black

@PeterAllenWebb-있을 것이지만 숫자가 임의의 순서로 정렬되어 있습니까?
James Black

1

숨겨진 요소는 상당히 크지 만 O (n) 시간과 O (1) 추가 공간에서 할 수 있습니다. 이것은 문제를 해결하는 실용적인 방법은 아니지만 그럼에도 불구하고 흥미로울 수 있습니다.

모든 부호없는 64 비트 정수 (오름차순)에 대해 대상 정수를 찾거나 목록 끝에 도달 할 때까지 목록을 반복합니다. 목록 끝에 도달하면 대상 정수는 목록에없는 가장 작은 정수입니다. 64 비트 정수의 끝에 도달하면 모든 64 비트 정수가 목록에 있습니다.

다음은 Python 함수입니다.

def smallest_missing_uint64(source_list):
    the_answer = None

    target = 0L
    while target < 2L**64:

        target_found = False
        for item in source_list:
            if item == target:
                target_found = True

        if not target_found and the_answer is None:
            the_answer = target

        target += 1L

    return the_answer

이 함수는 의도적으로 O (n)을 유지하는 데 비효율적입니다. 특히이 함수는 답을 찾은 후에도 대상 정수를 계속 확인합니다. 답이 발견 되 자마자 함수가 반환되면 외부 루프가 실행 된 횟수는 n으로 묶인 답의 크기에 의해 제한됩니다. 그 변경은 비록 훨씬 더 빠르더라도 실행 시간을 O (n ^ 2)로 만들 것입니다.


진실. O (1) 공간과 O (n) 시간 인 알고리즘 중 일부가 실제로이 질문에 실패하는 것은 참으로 재미 있습니다.
PeterAllenWebb

1

영감을 주신 egon, swilden 및 Stephen C에게 감사드립니다. 첫째, 목표 값이 목록의 크기보다 클 수 없기 때문에 목표 값의 경계를 알고 있습니다. 또한 1GB 목록에는 최대 134217728 (128 * 2 ^ 20) 64 비트 정수가 포함될 수 있습니다.

해싱 부분
나는 우리의 검색 공간을 극적으로 줄이기 위해 해싱을 사용하는 것을 제안합니다. 먼저 목록 크기의 제곱근입니다. 1GB 목록의 경우 N = 11,586입니다. 크기 N의 정수 배열을 설정합니다. 목록을 반복하고 찾은 각 숫자의 제곱근 *을 해시로 사용합니다. 해시 테이블에서 해당 해시에 대한 카운터를 증가시킵니다. 다음으로 해시 테이블을 반복합니다. 최대 크기와 같지 않은 첫 번째 버킷이 새 검색 공간을 정의합니다.

비트 맵 부분
이제 새 검색 공간의 크기와 동일한 일반 비트 맵을 설정하고 다시 소스 목록을 반복하여 검색 공간에서 각 숫자를 찾을 때 비트 맵을 채우십시오. 완료되면 비트 맵의 ​​첫 번째 설정되지 않은 비트가 답을 제공합니다.

이것은 O (n) 시간과 O (sqrt (n)) 공간에서 완료됩니다.

(* 비트 시프 팅과 같은 것을 사용하여이 작업을 훨씬 더 효율적으로 수행하고 그에 따라 버킷의 수와 크기를 변경할 수 있습니다.)


1
메모리 공간을 줄이기 위해 검색 공간을 Root-N 버킷으로 나누는 아이디어가 마음에 들지만 목록의 중복은이 방법을 망칠 수 있습니다. 고칠 수 있는지 궁금합니다.
PeterAllenWebb

맞습니다. 중복 항목을 고려하는 것을 무시했습니다. 그게 해결 될 수 있을지 모르겠습니다.
Nic

1

숫자 목록에 누락 된 숫자가 하나만있는 경우 누락 된 숫자를 찾는 가장 쉬운 방법은 시리즈를 더하고 목록의 각 값을 빼는 것입니다. 최종 값은 누락 된 숫자입니다.


네. 그것은 또 다른 고전적인 인터뷰 질문입니다.
PeterAllenWebb

1
목록에있는 숫자를 함께 XOR하고, 범위에있는 숫자를 함께 XOR하고, 결과를 함께 XOR하는 것보다 훨씬 쉽습니다.
John Kurlak 2014

1
 int i = 0;
            while ( i < Array.Length)
            {

                if (Array[i] == i + 1)
                {
                    i++;
                }

                if (i < Array.Length)
                {
                    if (Array[i] <= Array.Length)
                    {//SWap

                        int temp = Array[i];
                        int AnoTemp = Array[temp - 1];
                        Array[temp - 1] = temp;
                        Array[i] = AnoTemp;

                    }
                    else
                       i++;



                }
            }

            for (int j = 0; j < Array.Length; j++)
            {
                if (Array[j] > Array.Length)
                {
                    Console.WriteLine(j + 1);
                    j = Array.Length;
                }
                else
                    if (j == Array.Length - 1)
                        Console.WriteLine("Not Found !!");

            }
        }

1

해시 테이블을 사용하여 숫자를 저장할 수 있습니다. 모든 숫자가 완료되면 0부터 가장 낮은 숫자를 찾을 때까지 카운터를 실행합니다. 합리적으로 좋은 해시는 일정한 시간에 해시 및 저장하고 일정한 시간에 검색합니다.

for every i in X         // One scan Θ(1)
   hashtable.put(i, i);  // O(1)

low = 0;

while (hashtable.get(i) <> null)   // at most n+1 times
   low++;

print low;

n배열에 요소가있는 경우 최악의 경우 이며 {0, 1, ... n-1},이 경우 대답은에서 얻지 만 n여전히 유지합니다 O(n).


1

Java로 작성된 내 대답은 다음과 같습니다.

기본 아이디어 : 1- 중복 된 양수, 0 및 음수를 버리고 나머지를 합산하고 최대 양수도 얻고 맵에서 고유 한 양수를 유지하면서 배열을 반복합니다.

2- 합을 max * (max + 1) / 2로 계산합니다.

3- 1 단계와 2 단계에서 계산 된 합계의 차이 찾기

4- 1에서 [sums difference, max]의 최소값까지 다시 반복하고 1 단계에서 채운지도에없는 첫 번째 숫자를 반환합니다.

public static int solution(int[] A) {
    if (A == null || A.length == 0) {
        throw new IllegalArgumentException();
    }

    int sum = 0;
    Map<Integer, Boolean> uniqueNumbers = new HashMap<Integer, Boolean>();
    int max = A[0];
    for (int i = 0; i < A.length; i++) {
        if(A[i] < 0) {
            continue;
        }
        if(uniqueNumbers.get(A[i]) != null) {
            continue;
        }
        if (A[i] > max) {
            max = A[i];
        }
        uniqueNumbers.put(A[i], true);
        sum += A[i];
    }
    int completeSum = (max * (max + 1)) /  2;
    for(int j = 1; j <= Math.min((completeSum - sum), max); j++) {
        if(uniqueNumbers.get(j) == null) { //O(1)
            return j;
        }
    }
    //All negative case
    if(uniqueNumbers.isEmpty()) {
        return 1;
    }
    return 0;
}

0

Stephen C가 현명하게 지적했듯이 대답은 배열 길이보다 작은 숫자 여야합니다. 그런 다음 이진 검색으로 답을 찾을 수 있습니다. 이것은 최악의 경우를 최적화합니다 (그래서 면접관은 '가정'병리 적 시나리오에서 당신을 잡을 수 없습니다). 인터뷰에서 최악의 경우에 최적화하기 위해이 작업을 수행하고 있음을 지적하십시오.

이진 검색을 사용하는 방법은 배열의 각 요소에서 찾고있는 숫자를 빼고 부정적인 결과를 확인하는 것입니다.


0

나는 "추측 제로"장치를 좋아한다. 숫자가 무작위이면 0 일 가능성이 높습니다. "시험관"이 무작위가 아닌 목록을 설정 한 경우 하나를 추가하고 다시 추측하십시오.

LowNum=0
i=0
do forever {
  if i == N then leave /* Processed entire array */
  if array[i] == LowNum {
     LowNum++
     i=0
     }
   else {
     i++
   }
}
display LowNum

최악의 경우는 n = N 인 n * N이지만 실제로 n은 작은 숫자 일 가능성이 높습니다 (예 : 1).


0

질문이 있는지 확실하지 않습니다. 그러나 목록 1,2,3,5,6에 대해 누락 된 숫자가 4이면 누락 된 숫자는 O (n)에서 다음과 같이 찾을 수 있습니다. (n + 2) (n + 1) / 2- (n + 1) n / 2

편집 : 미안 해요, 어젯밤에 너무 빨리 생각하고 있었던 것 같아요. 어쨌든 두 번째 부분은 실제로 O (n)이 오는 sum (list)로 대체되어야합니다. 이 공식은 그 뒤에 숨은 아이디어를 보여줍니다. n 개의 연속 정수의 경우 합계는 (n + 1) * n / 2 여야합니다. 누락 된 숫자가있는 경우 합계는 (n + 1) 순차 정수의 합계에서 누락 된 숫자를 뺀 값과 같습니다.

마음 속에 중간 부분을 넣었다는 사실을 지적 해주셔서 감사합니다.


1
나는 이것이 어떻게 작동하는지 언뜻보기에 알지 못합니다. 귀하의 경우 n = 5이고 어떤 숫자가 누락 되어도 형식이 고정됩니다.
sisve

사이먼 : 제 편집에 따라 반대표를 제거해 주시겠습니까?
Codism

0

잘 했어요 Ants Aasma! 나는 약 15 분 동안 그 대답에 대해 생각했고, 당신과 비슷한 생각의 맥락에서 독립적으로 대답을 내놓았습니다.

#define SWAP(x,y) { numerictype_t tmp = x; x = y; y = tmp; }
int minNonNegativeNotInArr (numerictype_t * a, size_t n) {
    int m = n;
    for (int i = 0; i < m;) {
        if (a[i] >= m || a[i] < i || a[i] == a[a[i]]) {
            m--;
            SWAP (a[i], a[m]);
            continue;
        }
        if (a[i] > i) {
            SWAP (a[i], a[a[i]]);
            continue;
        }
        i++;
    }
    return m;
}

m은 "처음 i 입력에 대해 알고 있고 m-1에 입력 할 때까지 값에 대해 아무것도 가정하지 않은 경우 현재 가능한 최대 출력"을 나타냅니다.

이 m 값은 (a [i], ..., a [m-1])이 값 (i, ..., m-1)의 순열 인 경우에만 반환됩니다. 따라서 a [i]> = m 또는 a [i] <i 또는 a [i] == a [a [i]] 인 경우 m이 잘못된 출력이고 적어도 하나의 요소가 낮아야한다는 것을 압니다. 따라서 m을 줄이고 a [i]를 a [m]으로 바꾸면 재귀 할 수 있습니다.

이것이 사실이 아니라 a [i]> i이면 a [i]! = a [a [i]] a [i]를 a [a [i]]로 바꾸면 요소 수가 증가한다는 것을 알고 있습니다. 자신의 자리에서.

그렇지 않으면 a [i]는 i와 같아야합니다.이 경우이 인덱스까지 포함하는 모든 값이 인덱스와 같다는 것을 알고 i를 증가시킬 수 있습니다.

이것이 무한 루프에 들어갈 수 없다는 증거는 독자에게 연습으로 남겨집니다. :)


0

Dafny의 개미 '대답 쇼에서 조각은 왜 제자리 알고리즘이 실패 할 수 있습니다. requires전제 조건은 각 항목의 값이 배열의 범위를 넘어서는 안된다는 것을 설명합니다.

method AntsAasma(A: array<int>) returns (M: int)
  requires A != null && forall N :: 0 <= N < A.Length ==> 0 <= A[N] < A.Length;
  modifies A; 
{
  // Pass 1, move every value to the position of its value
  var N := A.Length;
  var cursor := 0;
  while (cursor < N)
  {
    var target := A[cursor];
    while (0 <= target < N && target != A[target])
    {
        var new_target := A[target];
        A[target] := target;
        target := new_target;
    }
    cursor := cursor + 1;
  }

  // Pass 2, find first location where the index doesn't match the value
  cursor := 0;
  while (cursor < N)
  {
    if (A[cursor] != cursor)
    {
      return cursor;
    }
    cursor := cursor + 1;
  }
  return N;
}

forall ...확인 오류를 보려면 절이 있거나없는 코드를 유효성 검사기에 붙여 넣으십시오 . 두 번째 오류는 검증자가 패스 1 루프에 대한 종료 조건을 설정할 수 없기 때문에 발생합니다. 이를 증명하는 것은 도구를 더 잘 이해하는 사람에게 맡겨집니다.


0

다음은 입력을 수정하지 않고 O (N) 시간 및 N 비트와 작은 일정한 메모리 오버 헤드 (여기서 N은 목록의 크기)를 사용하는 Java의 답변입니다.

int smallestMissingValue(List<Integer> values) {
    BitSet bitset = new BitSet(values.size() + 1);
    for (int i : values) {
        if (i >= 0 && i <= values.size()) {
            bitset.set(i);
        }
    }
    return bitset.nextClearBit(0);
}

0
def solution(A):

index = 0
target = []
A = [x for x in A if x >=0]

if len(A) ==0:
    return 1

maxi = max(A)
if maxi <= len(A):
    maxi = len(A)

target = ['X' for x in range(maxi+1)]
for number in A:
    target[number]= number

count = 1
while count < maxi+1:
    if target[count] == 'X':
        return count
    count +=1
return target[count-1] + 1

위의 솔루션에 대해 100 %를 얻었습니다.


0

1) 네거티브 및 제로 필터링

2) 정렬 / 구별

3) 방문 배열

복잡성 : O (N) 또는 O (N * log (N))

Java8 사용

public int solution(int[] A) {
            int result = 1;
    boolean found = false;
    A = Arrays.stream(A).filter(x -> x > 0).sorted().distinct().toArray();
    //System.out.println(Arrays.toString(A));
    for (int i = 0; i < A.length; i++) {
        result = i + 1;
        if (result != A[i]) {
            found = true;
            break;
        }
    }
    if (!found && result == A.length) {
        //result is larger than max element in array
        result++;
    }
    return result;
}

0

순서가 지정되지 않은 집합은 모든 양수를 저장하는 데 사용할 수 있으며, 1부터 순서없는 집합의 길이까지 반복하여 발생하지 않는 첫 번째 숫자를 볼 수 있습니다.

int firstMissingPositive(vector<int>& nums) {

    unordered_set<int> fre;
    // storing each positive number in a hash.
    for(int i = 0; i < nums.size(); i +=1)
    {
        if(nums[i] > 0)
            fre.insert(nums[i]);
     }

    int i = 1;
    // Iterating from 1 to size of the set and checking 
    // for the occurrence of 'i'

    for(auto it = fre.begin(); it != fre.end(); ++it)
    {
        if(fre.find(i) == fre.end())
            return i;
        i +=1;
    }

    return i;
}

0

기본 자바 스크립트를 통한 솔루션

var a = [1, 3, 6, 4, 1, 2];

function findSmallest(a) {
var m = 0;
  for(i=1;i<=a.length;i++) {
    j=0;m=1;
    while(j < a.length) {
      if(i === a[j]) {
        m++;
      }
      j++;
    }
    if(m === 1) {
      return i;
    }
  }
}

console.log(findSmallest(a))

이것이 누군가에게 도움이되기를 바랍니다.


0

파이썬을 사용하면 가장 효율적이지는 않지만 정확합니다.

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import datetime

# write your code in Python 3.6

def solution(A):
    MIN = 0
    MAX = 1000000
    possible_results = range(MIN, MAX)

    for i in possible_results:
        next_value = (i + 1)
        if next_value not in A:
            return next_value
    return 1

test_case_0 = [2, 2, 2]
test_case_1 = [1, 3, 44, 55, 6, 0, 3, 8]
test_case_2 = [-1, -22]
test_case_3 = [x for x in range(-10000, 10000)]
test_case_4 = [x for x in range(0, 100)] + [x for x in range(102, 200)]
test_case_5 = [4, 5, 6]
print("---")
a = datetime.datetime.now()
print(solution(test_case_0))
print(solution(test_case_1))
print(solution(test_case_2))
print(solution(test_case_3))
print(solution(test_case_4))
print(solution(test_case_5))

0
def solution(A):
    A.sort()
    j = 1
    for i, elem in enumerate(A):
        if j < elem:
            break
        elif j == elem:
            j += 1
            continue
        else:
            continue
    return j

0

이것은 도움이 될 수 있습니다 :

0- A is [5, 3, 2, 7];
1- Define B With Length = A.Length;                            (O(1))
2- initialize B Cells With 1;                                  (O(n))
3- For Each Item In A:
        if (B.Length <= item) then B[Item] = -1                (O(n))
4- The answer is smallest index in B such that B[index] != -1  (O(n))

이것은 Stephen C의 대답 과 다른 가요? 어떻게?
greybeard 19
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.