쉬운 면접 질문이 더 어려워졌습니다 : 1.100로 주어진 숫자, 정확하게 k가 주어진 누락 된 숫자 찾기


1146

나는 재미있는 직업 면접 경험을 가지고 있었다. 질문은 정말 쉽게 시작되었습니다.

Q1 : 우리는 숫자가 들어있는 가방을 가지고 1, 2, 3, ..., 100. 각 숫자는 정확히 한 번 표시되므로 100 개의 숫자가 있습니다. 이제 하나의 숫자가 가방에서 무작위로 선택됩니다. 빠진 번호를 찾으십시오.

물론이 인터뷰 질문을 들었으므로 다음과 같은 라인을 따라 매우 빠르게 대답했습니다.

A1 : 음, 숫자의 합 1 + 2 + 3 + … + N이다 (N+1)(N/2)(참조 : 산술 시리즈의 합을 위키 백과 ). 를 들어 N = 100, 합계입니다 5050.

따라서 모든 숫자가 백에 있으면 합계는 정확히 5050됩니다. 하나의 숫자가 누락되었으므로 합계는 이보다 작으며 차이는 그 숫자입니다. O(N)시간과 O(1)공간 에서 누락 된 숫자를 찾을 수 있습니다 .

이 시점에서 나는 내가 잘했다고 생각했지만 갑자기 질문이 예기치 않게 바뀌었다.

Q2 : 맞습니다. 이제 두 개의 숫자가 없으면 어떻게해야합니까?

나는 이전에이 변형을 보거나들은 적이 없었고, 그래서 당황했고 질문에 대답 할 수 없었다. 면접관은 내 사고 과정을 알고 있다고 주장 했으므로 예상 제품과 비교하여 더 많은 정보를 얻을 수 있거나 첫 번째 패스 등에서 정보를 수집 한 후 두 번째 패스를 수행 할 수 있다고 언급했지만 실제로 촬영 중이었습니다. 실제로 솔루션에 대한 명확한 경로를 갖는 것이 아니라 어둠 속에서.

면접관은 두 번째 방정식을 갖는 것이 실제로 문제를 해결하는 한 가지 방법이라고 말함으로써 저를 격려하려고했습니다. 이 시점에서 나는 일종의 화를 내었고 (사전 답변을 알지 못했기 때문에) 이것이 일반적인 (읽기 : "유용한") 프로그래밍 기술인지, 아니면 단지 트릭 / 고트 카 답변인지 물었다.

면접관의 대답은 나를 놀라게했다. 3 개의 누락 된 숫자를 찾기 위해 기술을 일반화 할 수있다. 실제로 k 개의 누락 된 숫자 를 찾기 위해 일반화 할 수 있습니다 .

Qk : 가방에 정확히 k 개의 숫자가 없으면 어떻게 효율적으로 찾을 수 있습니까?

몇 달 전 이었지만 여전히이 기술이 무엇인지 알 수 없었습니다. 분명히 Ω(N)모든 숫자를 한 번 이상 스캔해야하기 때문에 시간 하한이 있지만 면접관 은 해결 기술 의 시간공간 복잡성 ( O(N)시간 입력 스캔 제외)이 N이 아닌 k 로 정의되어 있다고 주장했습니다 .

따라서 여기의 질문은 간단합니다.

  • Q2를 어떻게 해결 하시겠습니까?
  • Q3를 어떻게 해결 하시겠습니까?
  • Qk를 어떻게 해결 하시겠습니까?

설명

  • 일반적으로있다 N 1 ..에서 번호 N 뿐 아니라 1..100은.
  • 비트 세트 사용 , 지정된 비트 값으로 각 숫자의 존재 여부를 인코딩하므로 O(N)추가 공간의 비트를 사용 하는 명확한 세트 기반 솔루션을 찾고 있지 않습니다 . N에 비례하여 추가 공간을 확보 할 수 없습니다 .
  • 또한 명확한 정렬 우선 접근 방식을 찾고 있지 않습니다. 이 방법과 세트 기반 접근 방식은 인터뷰에서 언급 할 가치가 있습니다 (구현하기 쉽고 N 에 따라 매우 실용적 일 수 있습니다). 나는 성배 솔루션을 찾고 있습니다 (구현하는 것이 실용적이지 않을 수도 있지만 그럼에도 불구하고 원하는 점근 적 특성을 가지고 있음).

물론 다시 입력을 스캔해야 O(N)하지만 ( N이 아닌 k로 정의 된) 소량의 정보 만 캡처 한 다음 어떻게 든 누락 된 k 를 찾아야합니다 .


7
@polygenelubricants 설명을 주셔서 감사합니다. "K가없는 숫자의 개수 인 O (N) 시간과 O (K) 공간을 사용하는 알고리즘을 찾고 있습니다. ;-)
Dave O.

7
Q1의 문에서 순서대로 숫자에 액세스 할 수 없다는 것을 정확하게해야합니다. 이것은 아마도 당신에게 명백한 것처럼 보이지만 나는 그 질문에 대해 들어 본 적이 없으며 "가방"이라는 용어 ( "멀티 세트"를 의미 함)는 다소 혼란 스러웠습니다.
Jérémie

7
여기에 제공된 답변이 우스운 것처럼 다음을 읽으십시오. stackoverflow.com/questions/4406110/…

18
제한되지 않는 정수에 대한 공간 요구 사항을 O (1)로 간주하지 않는 한 숫자를 합산하려면 log (N) 공간이 필요합니다. 그러나 무제한 정수를 허용하면 하나의 정수로 원하는만큼의 공간을 확보 할 수 있습니다.
우도 클라인

3
그건 그렇고 Q1에 대한 아주 좋은 대안 솔루션은 XOR에서 1까지 의 모든 숫자를 계산 n한 다음 주어진 배열의 모든 숫자로 결과를 xoring 할 수 있습니다 . 결국 당신은 빠진 번호가 있습니다. 이 솔루션에서는 요약과 같이 오버플로를 신경 쓸 필요가 없습니다.
sbeliakov

답변:


590

다음은 Dimitris Andreou의 링크에 대한 요약입니다 .

i = 1,2, .., k 인 i 번째 거듭 제곱의 합을 기억하십시오. 이것은 방정식 시스템을 해결하는 문제를 줄입니다.

a 1 + a 2 + ... + a k = b 1

a 1 2 + a 2 2 + ... + a k 2 = b 2

...

a 1 k + a 2 k + ... + a k k = b k

사용 뉴턴의 신원을 b를 알고, 내가 계산 할 수 있습니다

c 1 = a 1 + a 2 + ... a k

c 2 = a 1 a 2 + a 1 a 3 + ... + a k-1 a k

...

c k = a 1 a 2 ... a k

다항식 (xa 1 ) ... (xa k )을 확장하면 계수는 정확히 c 1 , ..., c k 가됩니다 . Viète의 공식을 참조하십시오 . 모든 다항식 요인이 고유하게 (다항식의 고리가 유클리드 영역 임), i 는 순열까지 고유하게 결정됨을 의미합니다 .

이것은 힘을 기억하는 것이 숫자를 회복하기에 충분하다는 증거를 끝냅니다. 상수 k의 경우이 방법이 좋습니다.

그러나, k가 변할 때, c 1 , ..., c k 를 계산하는 직접적인 접근법 은 엄청나게 비싸다. 예를 들어, c k 는 모든 누락 된 수, 크기 n! / (nk)!의 곱 이기 때문이다 . 이를 극복하려면 Z q 필드 에서 계산을 수행하십시오 . 여기서 q는 n <= q <2n이되도록 소수 입니다.- Bertrand의 가정에 의해 존재합니다 . 수식은 여전히 ​​유효하며 다항식의 인수 분해는 여전히 고유하므로 증거를 변경할 필요가 없습니다. 또한 유한 필드에 대한 인수 분해 알고리즘 (예 : Berlekamp 또는 Cantor-Zassenhaus 의 알고리즘)이 필요합니다 .

상수 k에 대한 고급 의사 코드 :

  • 주어진 숫자의 i 번째 거듭 제곱 계산
  • 알 수없는 숫자의 i 번째 거듭 제곱의 합계를 빼기 위해 빼십시오. 합을 불러라 b i .
  • B의 연산 계수에 사용 뉴턴의 정체성 ; 그들을 c i 라고 불러라 . 기본적으로, c 1 = b 1 ; c 2 = (c 1 b 1 -b 2 ) / 2; 정확한 공식은 Wikipedia를 참조하십시오.
  • 다항식 x k -c 1 x k-1 + ... + c k를 인수 분해 합니다.
  • 다항식의 근은 필요한 수 a 1 , ..., a k 입니다.

다양한 k의 경우, 예를 들어 Miller-Rabin을 사용하여 소수 n <= q <2n을 찾고 모든 수를 모듈로 q로 줄인 단계를 수행하십시오.

편집 :이 답변의 이전 버전은 Z q 대신 q가 소수 인 유한 특성 필드 2 (q = 2 ^ (log n))를 사용할 수 있다고 언급했습니다 . 뉴턴의 공식은 최대 k의 숫자로 나눌 필요가 있기 때문에 그렇지 않습니다.


6
프라임 필드를 사용할 필요는 없으며을 사용할 수도 있습니다 q = 2^(log n). (어떻게
위첨자

49
+1 이것은 정말, 영리합니다. 동시에 노력의 가치가 있는지 또는 인공적인 문제에 대한이 솔루션을 다른 방식으로 재사용 할 수 있는지 여부는 의문의 여지가 있습니다. 그리고 이것이 실제 문제 일지라도, 많은 플랫폼에서 가장 사소한 O(N^2)해결책은 아마도 아마도이 아름다움보다 훨씬 높은 성능을 보일 것입니다 N. 나를 생각하게한다 : tinyurl.com/c8fwgw 그럼에도 불구하고, 훌륭한 일! 나는 모든 수학을 통해 크롤링하는 인내심을 갖지 못했을 것입니다 :)
back2dos

167
나는 이것이 훌륭한 답변이라고 생각합니다. 나는 이것이 누락 된 숫자를 1 이상으로 확장하는 것이 얼마나 인터뷰 질문의 가난한지를 보여줍니다. 첫 번째조차도 일종의 장애가 있지만 기본적으로 "면접 준비를 했음"을 표시 할 정도로 일반적입니다. 그러나 CS 전공이 k = 1 (특히 인터뷰에서 "즉석에서")을 넘어 설 것을 기대하는 것은 약간 바보입니다.
corsiKa

5
이것은 입력에서 효과적으로 리드 솔로몬 코딩을 수행하는 것입니다.
David Ehrmann

78
모든 숫자를 입력하고 룩업을 사용 hash set하여 1...N스위트를 반복 하여 숫자가 누락되었는지 확인하고, k변형에 대해 가장 일반적이고 가장 빠르며 , 가장 디버깅 가능하고 유지 보수가 쉽고 이해하기 쉬운 솔루션입니다. 물론 수학 방식은 인상적이지만 수학자가 아닌 엔지니어가되어야합니다. 특히 사업이 관련된 경우.
v.oddou

243

Muthukrishnan-Data Stream Algorithms : Puzzle 1 : Finding Missing Numbers 페이지를 읽으면 찾을 수 있습니다 . 찾고있는 일반화를 정확하게 보여줍니다 . 아마도 이것은 당신의 면접관이 읽은 것과 아마도 이런 질문을 제기 한 이유 일 것입니다.

이제 사람들 만 Muthukrishnan의 치료법에 포함되거나 대체 된 답변을 삭제하기 시작하면이 텍스트를 더 쉽게 찾을 수 있습니다. :)


또한 의사 코드 (hurray! 까다로운 수학 공식을 읽을 필요가 없습니다 :)) (감사합니다.)가 포함 된 sdcvvc의 직접 관련 답변을 참조하십시오 .


우우 ... 흥미 롭습니다. 나는 수학에 약간 혼란스러워했다는 것을 인정해야하지만 나는 그것을 감추고 있었다. 나중에 더 볼 수 있도록 열어 둘 수 있습니다. :) 그리고 +1이 링크를 더 쉽게 찾을 수 있습니다. ;-)
Chris

2
Google 도서 링크가 작동하지 않습니다. 이곳까지 더 나은 버전 [포스트 스크립트 파일].
Heinrich Apfelmus

9
와. 나는 이것이 upvoted 기대하지 않았다! : 마지막으로 내가 대신 그것을 자신을 해결하기 위해 노력, 그것은 실제로을 downvoted되었다 (그 경우 크 누스의) 솔루션에 대한 참조를 게시 stackoverflow.com/questions/3060104/... 나를 기뻐 내부 감사를 사서을 :)
디미트리 Andreou을

@Apfelmus, 이것은 초안입니다. (물론 나는 책을 찾기 전에 거의 1 년 동안 실물에 대한 초안을 혼동했습니다). Btw 링크가 작동하지 않으면 books.google.com으로 이동하여 "Muthukrishnan 데이터 스트림 알고리즘"(따옴표 제외)을 검색하면 가장 먼저 팝업됩니다.
Dimitris Andreou

2
여기에 제공된 답변이 우스운 것처럼 다음을 읽으십시오. stackoverflow.com/questions/4406110/…

174

우리는 숫자 자체와 사각형 을 합하여 Q2를 풀 수 있습니다. 을 .

그런 다음 문제를 줄일 수 있습니다

k1 + k2 = x
k1^2 + k2^2 = y

어디서 x그리고y 합계가 예상 값보다 얼마나 멀리 있습니다.

대체는 다음을 제공합니다.

(x-k2)^2 + k2^2 = y

그런 다음 누락 된 숫자를 확인하기 위해 해결할 수 있습니다.


7
+1; 메이플에서 수식을 선택하여 숫자를 선택했는데 작동합니다. 그래도 왜 그것이 작동하는지 스스로를 설득 할 수 없었습니다.
polygenelubricants

4
@polygenelubricants : 당신은 정확성을 증명하고 싶었 경우 먼저 항상 제공하는 것을 보여 것 입니다, 항상 세트에서 제거 할 때 갖는 집합의 나머지 초래, 한 쌍의 숫자를 생성합니다 (올바른 솔루션을 관찰 된 합과 제곱합). 거기에서 고유성을 증명하는 것은 하나의 숫자 쌍만 생성한다는 것을 보여주는 것처럼 간단합니다.
아논.

5
방정식의 특성상 해당 방정식에서 k2의 두 값을 얻게됩니다. 그러나 k1을 생성하는 데 사용하는 첫 번째 방정식에서 k2의이 두 값이 k1이 다른 값임을 의미하므로 반대의 방법으로 같은 숫자의 두 해가 있음을 알 수 있습니다. k1> k2라고 임의로 선언하면 2 차 방정식에 대한 하나의 솔루션과 전체에 대한 하나의 솔루션 만 갖게됩니다. 그리고 분명히 질문의 본질에 따라 답이 항상 존재하므로 항상 작동합니다.
Chris

3
주어진 합 k1 + k2에 대해 많은 쌍이 있습니다. 이 쌍을 K1 = a + b 및 K2 = ab로 쓸 수 있습니다. 여기서 a = (K1 + k2 / 2). a는 주어진 합계에 대해 고유합니다. 제곱의 합 (a + b) ** 2 + (ab) ** 2 = 2 * (a 2 + b 2). 주어진 합 K1 + K2의 경우 a 2 항은 고정되어 있으며 b 2 항 으로 인해 제곱의 합이 고유하다는 것을 알 수 있습니다 . 따라서 x와 y 값은 정수 쌍에 대해 고유합니다.
phkahler

8
대단해. @ user3281743 예제가 있습니다. 누락 된 숫자 (k1 및 k2)를 4와 6으로 설정하십시오. Sum (1-> 10) = 55 및 Sum (1 ^ 2-> 10 ^ 2) = 385. 이제 x = 55-(Sum (모든 나머지 숫자) )) 및 y = 385-(Sum (모든 나머지 수의 제곱)) 따라서 x = 10 및 y = 52입니다. 표시된대로 다음과 같이 대체하십시오 : (10-k2) ^ 2 + k2 ^ 2 = 52 2k ^ 2-20k + 48 = 0으로 단순화합니다. 2 차 방정식을 풀면 4와 6이 답으로됩니다.
AlexKoren

137

@j_random_hacker가 지적했듯이 이것은 O (n) 시간과 O (1) 공간에서 중복 찾기 와 매우 유사 합니다. 내 대답의 적응도 여기에서 작동합니다.

"가방"이 1 기반 A[]크기의 배열 로 표현되어 있다고 가정하면 시간과 추가 공간 N - k에서 Qk를 해결할 수 있습니다 .O(N)O(k)

먼저 배열 A[]k요소 별로 확장 하여 크기가 커집니다 N. 이것은이다 O(k)추가 공간. 그런 다음 다음 의사 코드 알고리즘을 실행합니다.

for i := n - k + 1 to n
    A[i] := A[1]
end for

for i := 1 to n - k
    while A[A[i]] != A[i] 
        swap(A[i], A[A[i]])
    end while
end for

for i := 1 to n
    if A[i] != i then 
        print i
    end if
end for

첫 번째 루프는 k추가 항목을 배열의 첫 번째 항목과 동일하게 초기화합니다 (이것은 배열에 이미 존재하는 편리한 값입니다-이 단계 후에 크기의 초기 배열에서 누락 된 항목 N-k은 확장 배열에서 여전히 누락 됨).

두 번째 루프는 확장 배열을 치환하여 요소 x가 적어도 한 번 존재하면 해당 항목 중 하나가 위치에있게합니다.A[x] 됩니다.

중첩 루프가 있지만 여전히 O(N)시간에 실행됩니다 . 스왑은 다음 i과 같은 경우에만 발생 A[i] != i하며 각 스왑은 A[i] == i이전과 같지 않은 요소를 하나 이상 설정합니다 . 이는 총 스왑 수 (따라서 while루프 본문 의 총 실행 수 )가 최대 임을 의미합니다 N-1.

세 번째 루프 i는 값 i이 차지하지 않는 배열의 인덱스를 인쇄 i합니다. 이는 누락 된 것을 의미 합니다.


4
왜 그렇게 적은 사람들이이 답변에 투표하고 정답으로 표시하지 않았는지 궁금합니다. 다음은 파이썬 코드입니다. O (n) 시간에 실행되며 추가 공간 O (k)가 필요합니다. pastebin.com/9jZqnTzV
wall-e

3
@caf 이것은 비트를 설정하고 비트가 0 인 곳을 계산하는 것과 매우 유사합니다. 그리고 정수 배열을 만들면 더 많은 메모리가 사용됩니다.
Fox

5
"비트를 설정하고 비트가 0 인 장소를 세려면"O (n) 개의 추가 공간이 필요합니다.이 솔루션은 O (k)의 추가 공간을 사용하는 방법을 보여줍니다.
caf

7
스트림을 입력으로 사용하지 않고 입력 배열을 수정합니다 (매우 좋아하고 아이디어가 유익하지만).
comco

3
@ v.oddou : 아뇨, 괜찮습니다. 스왑이 변경 A[i]되어 다음 반복에서 이전 값과 동일한 두 값을 비교하지 않습니다. 새로운 A[i]것은 마지막 루프의 것과 A[A[i]]같지만 새로운 값 은 새로운 A[A[i]]것 입니다. 사용해보십시오.
caf

128

나는이 문제를 해결하기 위해 4 살짜리 아이에게 물었다. 그는 숫자를 정렬 한 다음 계산했습니다. 이것은 O (주방 층)의 공간 요구 사항을 가지고 있으며 많은 공이 누락 된 것처럼 쉽게 작동합니다.


20
;) 귀하의 4 세는 5 세 이상이어야하며 천재입니다. 내 4 살짜리 딸은 아직 4까지 제대로 세지 못합니다. 그녀가 "4"의 존재를 간신히 통합했다고 가정 해 봅시다. 그렇지 않으면 지금까지 그녀는 항상 그것을 건너 뛸 것입니다. "1,2,3,5,6,7"은 그녀의 일반적인 계산 순서였습니다. 나는 그녀에게 연필을 더하라고 요청했고 그녀는 처음부터 다시 번호를 다시 매겨 1 + 2 = 3을 관리 할 것이다. 나는 실제로 걱정한다 ... : '(meh ..
v.oddou

간단하면서도 효과적인 접근법.
PabTorre

6
O (주방 층) haha-그러나 O (n ^ 2)는 아닐까요?

13
O (평방 미터) 같아요 :)
빅토르 Mellgren

1
@phuclv : 대답은 "이 공간 은 O (주방 층) 의 공간이 필요합니다." 그러나 어쨌든, 이것은 O (n) 시간 내에 정렬을 수행 할 수 있는 인스턴스입니다 --- 이 토론을보십시오 .
Anthony Labarre

36

가장 효율적인 솔루션인지 확실하지 않지만 모든 항목을 반복하고 비트 세트를 사용하여 숫자를 설정 한 다음 0 비트를 테스트합니다.

나는 간단한 해결책을 좋아합니다. 심지어 합이나 제곱합 등을 계산하는 것보다 빠를 것이라고 믿습니다.


11
나는이 명백한 대답을 제안했지만, 이것은 면접관이 원하는 것이 아니다. 나는 질문에서 이것이 내가 찾고있는 대답이 아니라고 분명히 말했다. 또 다른 명백한 대답 : 먼저 정렬하십시오. 어느 쪽도 아니 O(N)계산 정렬도 O(N log N)비교 정렬은 모두 매우 간단한 솔루션은 있지만 내가 무엇을 찾고 있어요 없습니다.
polygenelubricants

@ polygenelubricants : 귀하의 질문에서 당신이 말한 곳을 찾을 수 없습니다. 비트 세트를 결과로 간주하면 두 번째 패스가 없습니다. 복잡성은 (면접자가 인터뷰에서 제안한 것처럼 복잡성이 " k가 아닌 N으로 정의 됨"이라고 말한 것처럼 N을 일정하다고 간주하는 경우 ) O (1)이며 더 "깨끗한"결과를 구성해야하는 경우 깨끗한 결과를 얻으려면 항상 O (k)가 필요하기 때문에 O (k)를 얻는 것이 가장 좋습니다.
Chris Lercher

"저는 명확한 세트 기반 솔루션을 찾고 있지 않습니다 (예 : 비트 세트 사용). 원래 질문의 두 번째 마지막 단락
hrnt

9
@hmt : 예, 몇 분 전에 질문이 편집되었습니다. 나는 인터뷰 대상자로부터 기대할 수있는 답을 제공하고있다 ... 인공적으로 차선책을 세우는 솔루션 (당신이 무엇을하든 O (n) + O (k) 시간을 이길 수는 없다) ' 추가 공간을 감당할 수없는 경우를 제외하고는 나에게 이해가되지 않지만 질문은 명확하지 않습니다.
Chris Lercher

3
더 명확히하기 위해 질문을 다시 편집했습니다. 피드백 / 응답에 감사합니다.
polygenelubricants 11

33

나는 수학을 확인하지는 않았지만 Σ(n^2)계산과 같은 패스로 계산 Σ(n)하면 누락 된 두 숫자를 얻을 수있는 충분한 정보를 제공 할 것이라고 생각합니다 Σ(n^3).3이있는 경우에도 수행하십시오.


15

숫자의 합계를 기반으로 한 솔루션의 문제는 큰 지수를 가진 숫자를 저장하고 사용하는 비용을 고려하지 않는다는 것입니다 ... 실제로, 매우 큰 n에 대해 작동하기 위해서는 큰 숫자 라이브러리가 사용됩니다 . 이러한 알고리즘의 공간 활용도를 분석 할 수 있습니다.

sdcvvc 및 Dimitris Andreou 알고리즘의 시간 및 공간 복잡성을 분석 할 수 있습니다.

저장:

l_j = ceil (log_2 (sum_{i=1}^n i^j))
l_j > log_2 n^j  (assuming n >= 0, k >= 0)
l_j > j log_2 n \in \Omega(j log n)

l_j < log_2 ((sum_{i=1}^n i)^j) + 1
l_j < j log_2 (n) + j log_2 (n + 1) - j log_2 (2) + 1
l_j < j log_2 n + j + c \in O(j log n)`

그래서 l_j \in \Theta(j log n)

사용 된 총 저장 용량 : \sum_{j=1}^k l_j \in \Theta(k^2 log n)

사용 된 공간 : 컴퓨팅에 a^j시간이 걸린다고 가정하면 ceil(log_2 j)총 시간 :

t = k ceil(\sum_i=1^n log_2 (i)) = k ceil(log_2 (\prod_i=1^n (i)))
t > k log_2 (n^n + O(n^(n-1)))
t > k log_2 (n^n) = kn log_2 (n)  \in \Omega(kn log n)
t < k log_2 (\prod_i=1^n i^i) + 1
t < kn log_2 (n) + 1 \in O(kn log n)

사용한 총 시간 : \Theta(kn log n)

이 시간과 공간이 만족 스러우면 간단한 재귀 알고리즘을 사용할 수 있습니다. b! i를 백에 넣은 i 번째 항목, n 제거 전의 수, k 제거 수를 보자. 하스켈 구문에서 ...

let
  -- O(1)
  isInRange low high v = (v >= low) && (v <= high)
  -- O(n - k)
  countInRange low high = sum $ map (fromEnum . isInRange low high . (!)b) [1..(n-k)]
  findMissing l low high krange
    -- O(1) if there is nothing to find.
    | krange=0 = l
    -- O(1) if there is only one possibility.
    | low=high = low:l
    -- Otherwise total of O(knlog(n)) time
    | otherwise =
       let
         mid = (low + high) `div` 2
         klow = countInRange low mid
         khigh = krange - klow
       in
         findMissing (findMissing low mid klow) (mid + 1) high khigh
in
  findMising 1 (n - k) k

사용 된 스토리지 : O(k)목록, O(log(n))스택 용 : O(k + log(n)) 이 알고리즘은보다 직관적이고 시간 복잡성이 동일하며 공간을 덜 사용합니다.


1
+1, 좋아 보이지만 스 니펫 # 1의 4 행에서 5 행으로가는 길을 잃었습니다. 더 자세히 설명해 주시겠습니까? 감사!
j_random_hacker 8

isInRangeO ( 1)이 아닌 O (log n) 입니다. 범위 1..n의 숫자를 비교하므로 O (log n) 비트 를 비교해야합니다 . 이 오류가 나머지 분석에 어느 정도 영향을 미치는지 모르겠습니다.
jcsahnwaldt는 GoFundMonica가

14

잠깐만 질문에 명시된 바와 같이, 가방에는 100 개의 숫자가 있습니다. k가 아무리 클지라도, 세트를 사용하고 최대 100k 루프의 반복에서 세트에서 숫자를 제거 할 수 있기 때문에 문제를 일정한 시간에 해결할 수 있습니다. 100은 일정합니다. 남은 숫자는 답입니다.

해를 1에서 N까지의 숫자로 일반화하면 N이 상수가 아닌 것을 제외하고는 아무것도 변하지 않으므로 O (N-k) = O (N) 시간입니다. 예를 들어, 비트 세트를 사용하는 경우 O (N) 시간에 비트를 1로 설정하고 숫자를 반복하여 비트를 0으로 설정합니다 (O (Nk) = O (N)). 대답하십시오.

면접관이 O (N) 시간 대신 O (k) 시간으로 최종 세트의 내용 을 인쇄 하는 방법을 묻고있는 것 같습니다 . 분명히 비트 세트를 사용하면 숫자를 인쇄할지 여부를 결정하기 위해 모든 N 비트를 반복해야합니다. 그러나 세트가 구현되는 방식을 변경하면 숫자를 반복하여 인쇄 할 수 있습니다. 이것은 해시 세트와 이중 링크리스트 모두에 저장 될 객체에 숫자를 넣어서 수행됩니다. 해시 세트에서 객체를 제거하면 목록에서도 객체가 제거됩니다. 정답은 현재 길이 k 인 목록에 남습니다.


9
이 답변은 너무 간단하며 간단한 답변이 효과가 없다는 것을 알고 있습니다. ;) 진지하게, 원래의 질문은 아마도 O (k) 공간 요구 사항을 강조해야한다.
DK.

문제는 간단하지 않지만 맵에 O (n) 추가 메모리를 사용해야한다는 것입니다. 끊임없는 시간과 일정한 기억 속에서 해결 된 문제
Mojo Risin

3
최소한의 솔루션이 최소한 O (N)임을 증명할 수 있습니다. 더 적기 때문에 일부 숫자를 보지도 않고 주문이 지정되지 않았으므로 모든 숫자를 보는 것이 필수적입니다.
v.oddou

입력을 스트림으로보고 n이 너무 커서 메모리에 보관할 수없는 경우 O (k) 메모리 요구 사항이 적합합니다. 우리는 여전히 해싱을 사용할 수 있습니다 : k ^ 2 버킷을 만들고 각각에 간단한 합계 알고리즘을 사용하십시오. 그것은 단지 k ^ 2 메모리이며 몇 개의 더 많은 버킷을 사용하여 높은 성공 확률을 얻을 수 있습니다.
Thomas Ahle

8

2 (및 3) 누락 숫자 질문을 해결하기 위해 파티션을 적절하게 수행하면 quickselect평균적으로 실행되고 O(n)일정한 메모리를 사용 하는을 수정할 수 있습니다 .

  1. 피벗 보다 작은 숫자를 포함하고 피벗보다 큰 숫자를 포함하는 p파티션 으로 임의 피벗과 관련하여 세트를 분할하십시오 .lr

  2. 피벗 값을 각 파티션의 크기 ( p - 1 - count(l) = count of missing numbers in ln - count(r) - p = count of missing numbers in r) 와 비교하여 누락 된 2 개의 숫자가있는 파티션을 결정하십시오.

  3. a) 각 파티션에 하나의 숫자가없는 경우 합계 차이 방법을 사용하여 누락 된 각 숫자를 찾으십시오.

    (1 + 2 + ... + (p-1)) - sum(l) = missing #1((p+1) + (p+2) ... + n) - sum(r) = missing #2

    하나 개의 파티션은 모두 번호가 누락 및 파티션이 비어있는 경우 b)에 다음 누락 된 숫자 중 하나입니다 (p-1,p-2)또는 (p+1,p+2) 파티션 번호가 누락에 따라.

    하나의 파티션에 2 개의 숫자가 없지만 비어 있지 않은 경우 해당 파티션으로 되풀이하십시오.

2 개의 누락 된 숫자 만 있으면이 알고리즘은 항상 하나 이상의 파티션을 삭제하므로 O(n)빠른 선택의 평균 시간 복잡성을 유지합니다 . 마찬가지로 3 개의 누락 된 숫자가있는 경우이 알고리즘은 각 패스와 함께 하나 이상의 파티션을 삭제합니다 (2 개의 누락 된 숫자와 마찬가지로 최대 1 개의 파티션에만 여러 개의 누락 된 숫자가 포함됨). 그러나 누락 된 숫자가 더 추가 될 때 성능이 얼마나 저하되는지 확실하지 않습니다.

내부 파티션을 사용 하지 않는 구현 이 있으므로이 예제는 공간 요구 사항을 충족시키지 않지만 알고리즘의 단계를 보여줍니다.

<?php

  $list = range(1,100);
  unset($list[3]);
  unset($list[31]);

  findMissing($list,1,100);

  function findMissing($list, $min, $max) {
    if(empty($list)) {
      print_r(range($min, $max));
      return;
    }

    $l = $r = [];
    $pivot = array_pop($list);

    foreach($list as $number) {
      if($number < $pivot) {
        $l[] = $number;
      }
      else {
        $r[] = $number;
      }
    }

    if(count($l) == $pivot - $min - 1) {
      // only 1 missing number use difference of sums
      print array_sum(range($min, $pivot-1)) - array_sum($l) . "\n";
    }
    else if(count($l) < $pivot - $min) {
      // more than 1 missing number, recurse
      findMissing($l, $min, $pivot-1);
    }

    if(count($r) == $max - $pivot - 1) {
      // only 1 missing number use difference of sums
      print array_sum(range($pivot + 1, $max)) - array_sum($r) . "\n";
    } else if(count($r) < $max - $pivot) {
      // mroe than 1 missing number recurse
      findMissing($r, $pivot+1, $max);
    }
  }

데모


세트를 분할하는 것은 선형 공간을 사용하는 것과 같습니다. 적어도 스트리밍 설정에서는 작동하지 않습니다.
Thomas Ahle

@ThomasAhle은 en.wikipedia.org/wiki/Selection_algorithm#Space_complexity를 참조하십시오 . 세트를 적절하게 분할하려면 선형 공간이 아닌 O (1) 추가 공간 만 필요합니다. 스트리밍 설정에서는 추가 공간이 O (k)이지만 원래 질문에는 스트리밍에 대한 언급이 없습니다.
FuzzyTree

직접적으로는 아니지만 "O (N)로 입력을 스캔해야하지만 일반적으로 스트리밍의 정의 인 소량의 정보 (k가 아닌 N으로 정의 됨) 만 캡처 할 수 있습니다"라고 기록합니다. N 크기의 배열이 없으면 분할을 위해 모든 숫자를 이동할 수는 없습니다. 질문에 많은 답변이 있다는 것은 마녀 가이 제약 조건을 무시하는 것 같습니다.
Thomas Ahle

1
그러나 더 많은 숫자를 추가하면 성능이 저하 될 수 있습니까? 우리는 또한 선형 시간 중앙값 알고리즘을 사용하여 항상 완벽한 컷을 얻을 수 있지만 k 숫자가 1, ..., n으로 잘 퍼져 있다면 자두하기 전에 "깊게"로킹 레벨을 진행하지 않아도됩니다 어떤 가지?
Thomas Ahle

2
최악의 경우의 실행 시간은 실제로 전체 입력 시간을 최대 logk 시간으로 처리해야하기 때문에 nlogk이며 기하학적 순서 (최대 n 개의 요소로 시작)입니다. 공간 요구 사항은 일반 재귀로 구현 될 때 로그온되지만 실제 빠른 선택을 실행하고 각 파티션의 올바른 길이를 보장하여 O (1)로 만들 수 있습니다.
emu

7

다음은 영리한 트릭없이 간단하게 k 비트의 추가 스토리지를 사용하는 솔루션입니다. 실행 시간 O (n), 추가 공간 O (k). 솔루션을 먼저 읽거나 천재가 아니라면이 문제를 해결할 수 있음을 증명하기 위해 :

void puzzle (int* data, int n, bool* extra, int k)
{
    // data contains n distinct numbers from 1 to n + k, extra provides
    // space for k extra bits. 

    // Rearrange the array so there are (even) even numbers at the start
    // and (odd) odd numbers at the end.
    int even = 0, odd = 0;
    while (even + odd < n)
    {
        if (data [even] % 2 == 0) ++even;
        else if (data [n - 1 - odd] % 2 == 1) ++odd;
        else { int tmp = data [even]; data [even] = data [n - 1 - odd]; 
               data [n - 1 - odd] = tmp; ++even; ++odd; }
    }

    // Erase the lowest bits of all numbers and set the extra bits to 0.
    for (int i = even; i < n; ++i) data [i] -= 1;
    for (int i = 0; i < k; ++i) extra [i] = false;

    // Set a bit for every number that is present
    for (int i = 0; i < n; ++i)
    {
        int tmp = data [i];
        tmp -= (tmp % 2);
        if (i >= even) ++tmp;
        if (tmp <= n) data [tmp - 1] += 1; else extra [tmp - n - 1] = true;
    }

    // Print out the missing ones
    for (int i = 1; i <= n; ++i)
        if (data [i - 1] % 2 == 0) printf ("Number %d is missing\n", i);
    for (int i = n + 1; i <= n + k; ++i)
        if (! extra [i - n - 1]) printf ("Number %d is missing\n", i);

    // Restore the lowest bits again.
    for (int i = 0; i < n; ++i) {
        if (i < even) { if (data [i] % 2 != 0) data [i] -= 1; }
        else { if (data [i] % 2 == 0) data [i] += 1; }
    }
}

원했습니까 (data [n - 1 - odd] % 2 == 1) ++odd;?
찰스

2
이것이 어떻게 작동하는지 설명해 주시겠습니까? 이해가 안 돼요
Teepeemm

임시 저장을 위해 (n + k) 부울 배열을 사용할 수 있다면 해결책은 매우 간단하지만 간단하지는 않습니다. 따라서 데이터를 다시 정렬하여 시작 부분에 짝수를, 배열 끝에 홀수를 넣습니다. 이제 n 개의 숫자 중 가장 낮은 비트를 임시 저장에 사용할 수 있습니다. 왜냐하면 짝수와 홀수가 얼마나 많은지 알고 가장 낮은 비트를 재구성 할 수 있기 때문입니다! 이 n 비트와 k 개의 추가 비트는 내가 필요한 (n + k) 부울입니다.
gnasher729

2
데이터가 너무 커서 메모리에 보관할 수없는 경우에는 작동하지 않으며 스트림으로 만 보았습니다. 맛있게 해키하지만 :)
토마스 Ahle

공간 복잡도는 O (1) 일 수 있습니다. 첫 번째 단계에서는 'extra'를 사용하지 않고 정확하게이 알고리즘으로 모든 숫자 <(n-k)를 처리합니다. 두 번째 단계에서는 패리티 비트를 다시 지우고 색인 번호 (nk) .. (n)에 첫 번째 k 위치를 사용합니다.
emu

5

모든 숫자가 존재하는지 확인할 수 있습니까? 그렇다면 다음을 시도하십시오.

S = 백에있는 모든 숫자의 합 (S <5050)
Z = 누락 된 숫자의 합 5050-S

누락 된 번호가있는 경우 xy다음 :

x = Z-y 및
최대 (x) = Z-1

당신의 범위를 확인 그래서 1max(x)하고 번호를 찾을 수


1
무엇 max(x)평균 때 x숫자는?
Thomas Ahle

2
그는 아마도 수의 세트에서 최대 의미
JavaHopper

만약 우리가 2 개 이상의 숫자를 가지고 있다면이 솔루션은 파열 될 것입니다
ozgeneral

4

이 알고리즘이 질문 1에서 작동 할 수 있습니다.

  1. 처음 100 개 정수의 사전 계산 xor (val = 1 ^ 2 ^ 3 ^ 4 .... 100)
  2. 입력 스트림에서 계속 오는 요소 x (val1 = val1 ^ next_input)
  3. 최종 답변 = val ^ val1

또는 더 나은 :

def GetValue(A)
  val=0
  for i=1 to 100
    do
      val=val^i
    done
  for value in A:
    do
      val=val^value 
    done
  return val

이 알고리즘은 실제로 두 개의 누락 된 숫자로 확장 될 수 있습니다. 첫 번째 단계는 동일하게 유지됩니다. 두 개의 누락 된 숫자로 GetValue를 호출하면 결과는 a1^a2두 개의 누락 된 숫자입니다. 의 말을하자

val = a1^a2

이제 val에서 a1과 a2를 체로 내기 위해 val의 모든 비트를 가져옵니다. ith비트가 val로 설정되어 있다고 합시다. 즉, a1과 a2는 ith비트 위치 에서 서로 다른 패리티를 갖 습니다. 이제 원래 배열에서 다른 반복을 수행하고 두 xor 값을 유지합니다. i 번째 비트가 설정된 숫자와 i 번째 비트가없는 숫자 중 하나입니다. 우리는 이제 두 개의 물통을 가지고 a1 and a2있으며 다른 물통에 놓일 보증합니다 . 이제 각 버킷에서 하나의 누락 된 요소를 찾기 위해 수행 한 것과 동일한 작업을 반복하십시오.


이것은의 문제를 해결합니다 k=1. 그러나 나는 xor합계를 사용 하는 것을 좋아 합니다. 약간 빠릅니다.
Thomas Ahle

@ThomasAhle 예. 나는 그것을 내 대답으로 불렀다.
bashrc

권리. k = 2에 대해 "2 차"xor가 무엇인지 알 수 있습니까? 합계에 제곱을 사용하는 것과 비슷하게 xor에 "제곱"을 사용할 수 있습니까?
Thomas Ahle

1
@ThomasAhle 2 개의 누락 된 숫자에 대해 작동하도록 수정했습니다.
bashrc

이것은 내가 가장 좋아하는 방법입니다 :)
robert king

3

두 목록의 합과 두 목록의 곱이 모두 있으면 Q2를 해결할 수 있습니다.

(l1은 원본이고 l2는 수정 된 목록입니다)

d = sum(l1) - sum(l2)
m = mul(l1) / mul(l2)

산술 시리즈의 합은 첫 번째 및 마지막 항 평균의 n 배이므로 다음과 같이 최적화 할 수 있습니다.

n = len(l1)
d = (n/2)*(n+1) - sum(l2)

이제 우리는 (a와 b가 제거 된 숫자라면)

a + b = d
a * b = m

따라서 다음과 같이 재정렬 할 수 있습니다.

a = s - b
b * (s - b) = m

그리고 곱하십시오 :

-b^2 + s*b = m

오른쪽이 0이되도록 재정렬하십시오.

-b^2 + s*b - m = 0

그런 다음 2 차 공식으로 해결할 수 있습니다.

b = (-s + sqrt(s^2 - (4*-1*-m)))/-2
a = s - b

샘플 Python 3 코드 :

from functools import reduce
import operator
import math
x = list(range(1,21))
sx = (len(x)/2)*(len(x)+1)
x.remove(15)
x.remove(5)
mul = lambda l: reduce(operator.mul,l)
s = sx - sum(x)
m = mul(range(1,21)) / mul(x)
b = (-s + math.sqrt(s**2 - (-4*(-m))))/-2
a = s - b
print(a,b) #15,5

나는 sqrt의 복잡도를 알고, 축소 및 합산 기능을 사용하지 않으므로이 솔루션의 복잡성을 해결할 수 없습니다 (아는 사람이 있다면 아래에 의견을 말하십시오).


계산에 얼마나 많은 시간과 메모리가 사용 x1*x2*x3*...됩니까?
Thomas Ahle

@ThomasAhle 목록의 길이는 O (n)-시간이고 O (1)-공간이지만 실제로는 (적어도 파이썬에서는) 곱셈은 O (n ^ 1.6)-시간입니다. 숫자와 숫자는 길이에서 O (log n) 공간입니다.
Tuomas Laakkonen

@ThomasAhle 아니오, log (a ^ n) = n * log (a) 따라서 숫자를 저장할 O (l log k) 공간이 있습니다. 따라서 길이 l과 길이 k의 원래 숫자 목록이 주어지면 O (l)-공간이 있지만 상수 팩터 (log k)는 모두 그것들을 쓰는 것보다 낮습니다. (내 방법이 질문에 대답하는 데 특히 좋은 방법이라고 생각하지 않습니다.)
Tuomas Laakkonen

3

Q2의 경우 다른 솔루션보다 비효율적이지만 여전히 O (N) 런타임이 있고 O (k) 공간을 차지하는 솔루션입니다.

아이디어는 원래 알고리즘을 두 번 실행하는 것입니다. 첫 번째는 누락 된 총 수를 얻으므로 누락 된 숫자의 상한을 얻습니다. 이 번호로 전화합시다 N. 잃어버린 두 숫자는 합산 N되므로 첫 번째 숫자는 구간 [1, floor((N-1)/2)]에있을 수 있고 두 번째 숫자는에있을 것입니다 [floor(N/2)+1,N-1].

따라서 첫 번째 간격에 포함되지 않은 모든 숫자를 버리고 모든 숫자를 다시 반복합니다. 즉, 당신은 그들의 합계를 추적합니다. 마지막으로 누락 된 두 숫자 중 하나와 두 번째 숫자를 알 수 있습니다.

이 방법이 일반화 될 수 있다고 생각하고 입력을 한 번 통과하는 동안 여러 검색이 "병렬"로 실행될 수는 있지만 아직 방법을 찾지 못했습니다.


Ahaha 예, 이것은 Q2에 대해 생각해 낸 것과 동일한 솔루션입니다 .N / 2 이하의 모든 숫자에 대해 음수 를 다시 취하는 합계를 계산하는 것만으로 도 더 좋습니다!
xjcl

2

복잡한 수학적 방정식과 이론 없이도 이것이 가능하다고 생각합니다. 다음은 적절한 O (2n) 시간 복잡성 솔루션에 대한 제안입니다.

입력 양식 가정 :

백에있는 숫자의 수 = n

누락 된 숫자 수 = k

백의 숫자는 길이 n의 배열로 표시됩니다.

알고리즘에 대한 입력 배열의 길이 = n

배열에서 누락 된 항목 (백에서 가져온 숫자)은 배열에서 첫 번째 요소의 값으로 대체됩니다.

예 : 처음에는 가방이 [2,9,3,7,8,6,4,5,1,10]처럼 보입니다. 4를 꺼내면 4의 값이 2 (배열의 첫 번째 요소)가됩니다. 따라서 4를 꺼내면 가방은 [2,9,3,7,8,6,2,5,1,10]처럼 보입니다.

이 솔루션의 핵심은 배열을 탐색 할 때 해당 INDEX의 값을 무시하여 방문한 숫자의 INDEX에 태그를 지정하는 것입니다.

    IEnumerable<int> GetMissingNumbers(int[] arrayOfNumbers)
    {
        List<int> missingNumbers = new List<int>();
        int arrayLength = arrayOfNumbers.Length;

        //First Pass
        for (int i = 0; i < arrayLength; i++)
        {
            int index = Math.Abs(arrayOfNumbers[i]) - 1;
            if (index > -1)
            {
                arrayOfNumbers[index] = Math.Abs(arrayOfNumbers[index]) * -1; //Marking the visited indexes
            }
        }

        //Second Pass to get missing numbers
        for (int i = 0; i < arrayLength; i++)
        {                
            //If this index is unvisited, means this is a missing number
            if (arrayOfNumbers[i] > 0)
            {
                missingNumbers.Add(i + 1);
            }
        }

        return missingNumbers;
    }

너무 많은 메모리를 사용합니다.
Thomas Ahle

2

이와 같은 스트리밍 알고리즘을 일반화하는 일반적인 방법이 있습니다. 아이디어는 약간의 무작위 화를 사용하여 k요소를 독립적 인 하위 문제로 '확산'하여 원래의 알고리즘으로 문제를 해결하는 것입니다. 이 기술은 무엇보다도 희박한 신호 재구성에 사용됩니다.

  • a크기 의 배열을 만듭니다 u = k^2.
  • 어떤 선택 보편적 인 해시 함수를 , h : {1,...,n} -> {1,...,u}. ( 곱하기 쉬프트 처럼 )
  • 각각의 경우 i1, ..., n증가a[h(i)] += i
  • x입력 스트림의 각 숫자 에 대해 감소 a[h(x)] -= x합니다.

모든 누락 된 숫자가 다른 버킷으로 해시 된 경우, 배열의 0이 아닌 요소는 이제 누락 된 숫자를 포함합니다.

특정 쌍이 동일한 버킷으로 전송 될 확률 1/u은 범용 해시 함수의 정의 보다 적습니다 . 약 k^2/2쌍이 있기 때문에 오류 확률이 최대 k^2/2/u=1/2입니다. 즉, 우리는 적어도 50 %의 확률로 성공하고, 증가하면 u기회가 증가합니다.

이 알고리즘은 k^2 logn약간의 공간을 필요로합니다. (우리 logn는 배열 버킷 당 비트 가 필요합니다 .) 이것은 @Dimitris Andreou의 답변에 필요한 공간과 일치합니다 (특히 다항식 인수 분해의 공간 요구 사항, 무작위 화됨). k전력 공급의 경우 시간 이 아닌 업데이트 당 시간 .

실제로 주석에 설명 된 트릭을 사용하여 전력 합 방법보다 훨씬 효율적일 수 있습니다.


참고 : 머신에서 속도가 더 빠르다면 xor각 버킷에서 사용할 수도 있습니다 sum.
Thomas Ahle

흥미롭지 만 k <= sqrt(n)적어도이 경우 공간 제약을 존중한다고 생각 합니다 u=k^2. k = 11 및 n = 100이라고 가정하면 121 개의 버킷이 있고 알고리즘은 스트림에서 각 #을 읽을 때 체크 오프하는 100 비트 배열을 갖는 것과 비슷합니다. 늘리면 u성공 가능성이 높아지지만 공간 제한을 초과하기 전에 얼마나 많이 늘릴 수 있는지에 제한이 있습니다.
FuzzyTree

1
이 문제는 n보다 훨씬 더 큰 의미가 k있다고 생각하지만 실제로 k logn해싱과 매우 유사한 방법으로 시간을 일정하게 유지하면서 공간을 확보 할 수 있다고 생각 합니다. 그것은 powers 방법과 같이 gnunet.org/eppstein-set-reconciliation에 설명되어 있지만 기본적으로 테이블 해싱과 같은 강력한 해시 함수를 사용하여 'k 중 2 개'버킷에 해시하므로 일부 버킷에는 하나의 요소 만 있습니다. . 디코딩하려면 해당 버킷을 식별하고 두 버킷 모두에서 요소를 제거하여 다른 버킷을 해제합니다.
Thomas Ahle

2

Q2에 대한 매우 간단한 해결책은 아무도 대답하지 않은 것에 놀랐습니다. Q1의 방법을 사용하여 누락 된 두 숫자의 합을 찾으십시오. S로 표시하면 누락 된 숫자 중 하나가 S / 2보다 작고 다른 숫자가 S / 2 (duh)보다 큽니다. 1에서 S / 2까지의 모든 숫자를 합산하여 수식 결과 (Q1의 방법과 유사)와 비교하여 누락 된 숫자 사이에서 더 낮은 숫자를 찾으십시오. 더 큰 결 측값을 찾으려면 S에서 빼십시오.


나는 이것이 Svalorzen의 대답 과 동일하다고 생각 하지만 더 나은 말로 설명했습니다. Qk로 일반화하는 방법에 대한 아이디어가 있습니까?
John McClane

다른 답변이 누락되어 죄송합니다. $ Q_k $로 일반화 할 수 있는지 확실하지 않습니다.이 경우 가장 작은 누락 요소를 특정 범위에 바인딩 할 수 없기 때문입니다. 일부 요소는 $ S / k $보다 작아야하지만 여러 요소에 해당 될 수 있습니다.
Gilad Deutsch

1

아주 좋은 문제입니다. Qk에 대해 설정된 차이를 사용하려고합니다. Ruby에서와 같이 많은 프로그래밍 언어가이를 지원합니다.

missing = (1..100).to_a - bag

아마도 가장 효율적인 솔루션은 아니지만이 경우 이러한 작업 (알려진 경계, 낮은 경계)에 직면 한 경우 실제 생활에서 사용할 것입니다. 숫자 집합이 매우 클 경우 물론 더 효율적인 알고리즘을 고려할 것이지만 그때까지 간단한 해결책으로 충분합니다.


1
너무 많은 공간을 사용합니다.
Thomas Ahle

@ThomasAhle : 두 번째 답변마다 쓸모없는 설명을 추가하는 이유는 무엇입니까? 너무 많은 공간을 사용한다는 것은 무슨 의미입니까?
DarkDust

"N에 비례하여 추가 공간을 확보 할 수 없습니다"라는 질문이 있기 때문입니다. 이 솔루션은 정확히 그렇게합니다.
Thomas Ahle

1

블룸 필터를 사용해 볼 수 있습니다. 백에있는 각 숫자를 블룸에 넣은 다음 각 번호를 찾을 수 없을 때까지 완전한 1-k 세트를 반복합니다. 모든 시나리오에서 답을 찾을 수는 없지만 충분한 해결책이 될 수 있습니다.


카운팅 블룸 필터도있어 삭제할 수 있습니다. 그런 다음 모든 숫자를 추가하고 스트림에 표시되는 숫자를 삭제할 수 있습니다.
Thomas Ahle

하하 이것은 아마도 더 실용적인 답변 중 하나이지만 거의 관심을 기울이지 않습니다.
ldog

1

나는 그 질문에 다른 접근 방식을 취하고 그가 해결하려는 더 큰 문제에 대한 자세한 내용을 위해 면접관을 조사합니다. 문제와 문제를 둘러싼 요구 사항에 따라 분명한 세트 기반 솔루션이 옳을 수도 있고 나중에 생성 및 선택 후 생성 방식이 적합하지 않을 수도 있습니다.

예를 들어, 면접관이 n메시지 k를 발송할 것이므로 답장을 보내지 않았 음을 알 수 있어야하고 . 그 후, 세트에는 누락 된 요소 목록이 포함되며 추가 처리가 필요하지 않습니다.n-k 답장이 도착한 . 또한 메시지 채널의 특성상 전체 보어에서 실행하더라도 마지막 응답이 도착한 후 최종 결과를 생성하는 데 걸리는 시간에 영향을 미치지 않으면 서 메시지간에 약간의 처리를 수행 할 수있는 충분한 시간이 있다고 가정 해 봅시다. 그 시간은 각각의 전송 된 메시지의 식별 패싯을 세트에 삽입하고 각각의 대응하는 응답이 도착할 때 삭제하는 데 사용할 수 있습니다. 마지막 응답이 도착하면, 유일한 구현은 전형적인 구현에서 필요한 식별자를 세트에서 제거하는 것입니다.O(log k+1)k

이것은 모든 것이 실행되기 때문에 미리 생성 된 숫자 백을 일괄 처리하는 가장 빠른 방법은 아닙니다 O((log 1 + log 2 + ... + log n) + (log n + log n-1 + ... + log k)). 그러나 k(미리 알 수없는 경우에도) 모든 값에서 작동하며 위의 예에서는 가장 중요한 간격을 최소화하는 방식으로 적용되었습니다.


여분의 메모리가 O (k ^ 2) 인 경우이 작동합니까?
Thomas Ahle

1

대칭 (그룹, 수학 언어)의 관점에서 솔루션을 생각함으로써 동기를 부여 할 수 있습니다. 일련의 숫자 순서에 상관없이 답은 동일해야합니다. k누락 된 요소를 판별하는 데 도움이되는 함수 를 사용 하려는 경우 해당 특성이있는 함수 (대칭)에 대해 생각해야합니다. 이 함수 s_1(x) = x_1 + x_2 + ... + x_n는 대칭 함수의 예이지만 더 높은 수준의 다른 함수가 있습니다. 특히 기본 대칭 함수를 고려하십시오 . 차수 2의 기본 대칭 함수 s_2(x) = x_1 x_2 + x_1 x_3 + ... + x_1 x_n + x_2 x_3 + ... + x_(n-1) x_n는 두 요소의 모든 곱의 합입니다. 3 급 이상의 초등 대칭 함수에 대해서도 유사합니다. 그것들은 분명히 대칭입니다. 또한 모든 대칭 기능의 빌딩 블록으로 밝혀졌습니다.

주목하면서 기본 대칭 함수를 작성할 수 있습니다 s_2(x,x_(n+1)) = s_2(x) + s_1(x)(x_(n+1)). 더 많은 생각은 당신을 확신 s_3(x,x_(n+1)) = s_3(x) + s_2(x)(x_(n+1))시켜서 한 번에 계산할 수 있도록합니다.

배열에서 누락 된 항목을 어떻게 알 수 있습니까? 다항식에 대해 생각하십시오 (z-x_1)(z-x_2)...(z-x_n). 0숫자를 입력하면 평가됩니다 x_i. 다항식을 확장하면 얻을 수 z^n-s_1(x)z^(n-1)+ ... + (-1)^n s_n있습니다. 기초 대칭 함수도 여기에 나타납니다. 뿌리에 순열을 적용하면 다항식이 동일하게 유지되기 때문에 놀랍지 않습니다.

따라서 우리는 다항식을 작성하고 다른 사람들이 언급했듯이 집합에 포함되지 않은 숫자를 파악하기 위해 그것을 고려할 수 있습니다.

마지막으로, 우리가 많은 수의 메모리 넘침 (n 번째 대칭 다항식이 순서가 될 것임)이 걱정된다면 100!, 100보다 큰 소수 인 mod p곳에서 이러한 계산을 수행 할 수 있습니다 p.이 경우 다항식을 평가하고 mod p다시 평가합니다. 에 대한 0입력은 일련의 숫자이며, 입력 번호가 설정되지 않을 때 그것이 0이 아닌 값으로 평가할 때. 다른 사람들이 지적 그러나,에 따라 시간에 다항식에서 값을 얻기 위해 k,하지 N, 우리는 다항식 요인에 있습니다 mod p.


1

또 다른 방법은 잔차 그래프 필터링을 사용하는 것입니다.

1에서 4까지의 숫자가 있고 3이 없다고 가정합니다. 이진 표현은 다음과 같습니다.

1 = 001b, 2 = 010b, 3 = 011b, 4 = 100b

그리고 다음과 같은 순서도를 만들 수 있습니다.

                   1
             1 -------------> 1
             |                | 
      2      |     1          |
0 ---------> 1 ----------> 0  |
|                          |  |
|     1            1       |  |
0 ---------> 0 ----------> 0  |
             |                |
      1      |      1         |
1 ---------> 0 -------------> 1

플로우 그래프는 x 노드를 포함하고 x는 비트 수입니다. 그리고 최대 모서리 수는 (2 * x) -2입니다.

따라서 32 비트 정수의 경우 O (32) 공간 또는 O (1) 공간이 필요합니다.

이제 1,2,4부터 시작하여 각 숫자의 용량을 제거하면 잔차 그래프가 남습니다.

0 ----------> 1 ---------> 1

마지막으로 다음과 같은 루프를 실행합니다.

 result = []
 for x in range(1,n):
     exists_path_in_residual_graph(x)
     result.append(x)

이제 결과 result에는 누락되지 않은 숫자가 포함됩니다 (거짓 긍정적). 그러나 누락 된 요소 가있는 경우 k <= (결과 크기) <= nk 입니다.

마지막으로 주어진 목록을 살펴보고 결과가 누락되었는지 표시합니다.

따라서 시간 복잡도는 O (n)입니다.

마지막으로, 노드 복용에 의해 위양성 (및 필요한 공간)의 수를 줄일 수있다 00, 01, 11, 10대신의 01.


그래프 다이어그램을 이해하지 못합니다. 노드, 모서리 및 숫자는 무엇을 나타 냅니까? 왜 일부 모서리가 다른 모서리가 아닌가?
다인

사실 나는 당신의 대답을 전혀 이해하지 못합니다. 좀 더 명확히 할 수 있습니까?
다인

1

O (k)의 의미에 대한 설명이 필요할 것입니다.

다음은 임의의 k에 대한 사소한 해결책입니다. 숫자 집합의 각 v에 대해 2 ^ v의 합을 누적하십시오. 마지막으로 i를 1에서 N으로 루프합니다. 2 ^ i로 AND AND AND 비트 합계가 0이면 i가 없습니다. (또는 합계를 2 ^ i로 나눈 값이 짝수 인 경우 숫자로 표시됩니다. 또는 sum modulo 2^(i+1)) < 2^i.)

쉬워요? O (N) 시간, O (1) 저장 및 임의 k를 지원합니다.

실제 컴퓨터에서 각각 O (N) 공간이 필요한 엄청난 수를 계산하는 것을 제외하고. 실제로이 솔루션은 비트 벡터와 동일합니다.

그래서 당신은 영리하고 합과 제곱의 합과 큐브의 합을 계산할 수 있습니다 ... v ^ k의 합까지, 그리고 멋진 수학을 수행하여 결과를 추출하십시오. 그러나 이것 또한 큰 숫자이며, 우리는 어떤 추상적 인 작동 모델을 이야기하고 있습니까? O (1) 공간에 어느 정도 적합하며 필요한 크기의 숫자를 합산하는 데 얼마나 걸립니까?


좋은 대답입니다! 한 가지 작은 것 : "sum modulo 2 ^ i가 0이면 i가 없습니다"가 올바르지 않습니다. 그러나 의도 한 것이 분명합니다. "sum modulo 2 ^ (i + 1)이 2 ^ i보다 작 으면 i가 빠졌다"고 생각합니다. (물론, 대부분의 프로그래밍 언어에서 우리가 대신 모듈로 계산의 이동 비트 사용하는 것이 때로는 언어가 조금 더 표현 일반적인 수학 표기법보다 프로그래밍 :-)..)
jcsahnwaldt는 GoFundMonica 말한다

1
고마워, 당신은 완전히 맞아! 나는 게으르고 수학 표기법에서 벗어 났지만 고쳤습니다. 다시
고치기

1

다음은 sdcvvc / Dimitris Andreou의 답변처럼 복잡한 수학에 의존하지 않고 caf 및 Panic 대령과 마찬가지로 입력 배열을 변경하지 않으며 Chris Lercher, JeremyP 및 많은 사람들이 그랬습니다. 기본적으로 저는 Q2에 대한 Svalorzen / Gilad Deutch의 아이디어로 시작하여 일반적인 사례 Qk로 일반화하고 알고리즘이 작동 함을 증명하기 위해 Java로 구현했습니다.

아이디어

임의의 간격 I 이 있고 그 중 적어도 하나의 누락 된 숫자 만 포함한다는 것을 알고 있다고 가정하십시오 . 입력 배열을 통해 하나 개의 패스 만 행 번호를 찾고 I , 우리는 합산 모두 얻을 수 S 와 양 Q 로부터 숫자 누락 I를 . 우리는 단순히 감소시키는하여이 작업을 수행 I의 길이 우리의 숫자가 발생할 때마다 I (얻기 위해 Q를 ) 및 모든 숫자의 미리 계산 된 금액을 줄임으로써 나는 그 발생 수 (얻기위한 각 시간 S를 ).

이제 SQ를 봅니다. 경우 Q = 1 , 그 다음 것을 의미 I은 단지 포함 누락 번호 중 하나,이 숫자는 명확 S . 우리는 내가 완성 된 것으로 표시 하고 (프로그램에서 "명확한"이라고 함) 추가 고려에서 제외합니다. 반면에 Q> 1 이면 I에 포함 된 누락 된 숫자 의 평균 A = S / Q 를 계산할 수 있습니다 . 모든 숫자가 고유하기 때문에 이러한 숫자 중 적어도 하나는 A 보다 엄격하게 작고 하나 이상은 A 보다 엄격히 큽니다 . 이제 우리는 분할 I을각각 하나 이상의 누락 된 숫자를 포함하는 두 개의 작은 간격으로. 정수인 경우 A 를 할당 하는 간격은 중요하지 않습니다 .

다음 배열 패스 는 각 간격에 대해 SQ 를 개별적으로 계산 하지만 (같은 패스로) 그 이후에는 Q = 1의 마크 간격과 Q> 1의 분할 간격을 만듭니다. 새로운 "모호한"간격이 없어 질 때까지이 과정을 계속합니다. 즉, 각 간격에 정확히 하나의 누락 된 숫자가 포함되어 있기 때문에 분할 할 것이 없습니다 (우리는 S 를 알고 있기 때문에 항상이 숫자를 알고 있습니다 ). 가능한 모든 숫자를 포함하는 유일한 "전체 범위"간격 에서 시작합니다 (문제의 [1..N] 등 ).

시간 및 공간 복잡성 분석

프로세스가 멈출 때까지해야하는 총 패스 수 p 는 누락 된 수 count k 보다 크지 않습니다 . 불평등 p <= k 는 엄격하게 증명 될 수 있습니다. 한편, k의 큰 값에 유용한 경험적 상한 p <log 2 N + 3도 있습니다. 입력 배열의 각 숫자를 이진 검색하여 해당 배열이 속하는 간격을 결정해야합니다. 이것은 시간 복잡성에 로그 k 승수를 추가합니다 .

총 시간 복잡도는 O (N ᛫ min (k, log N) ᛫ log k) 입니다. 큰 k의 경우 이는 sdcvvc / Dimitris Andreou의 방법 (O (N ᛫ k)) 보다 훨씬 낫습니다 .

이 작업을 위해서는 알고리즘 에 최대 k 간격 을 저장하기위한 O (k) 추가 공간이 필요 합니다. 이는 "비트 세트"솔루션에서 O (N) 보다 훨씬 낫습니다 .

자바 구현

위의 알고리즘을 구현하는 Java 클래스가 있습니다. 항상 누락 된 숫자 의 정렬 된 배열을 반환 합니다. 게다가 , 첫 번째 패스에서 계산하기 때문에 누락 된 숫자 카운트 k가 필요하지 않습니다 . 숫자의 전체 범위는 minNumberand maxNumber매개 변수로 지정됩니다 (예 : 문제의 첫 번째 예의 경우 1과 100).

public class MissingNumbers {
    private static class Interval {
        boolean ambiguous = true;
        final int begin;
        int quantity;
        long sum;

        Interval(int begin, int end) { // begin inclusive, end exclusive
            this.begin = begin;
            quantity = end - begin;
            sum = quantity * ((long)end - 1 + begin) / 2;
        }

        void exclude(int x) {
            quantity--;
            sum -= x;
        }
    }

    public static int[] find(int minNumber, int maxNumber, NumberBag inputBag) {
        Interval full = new Interval(minNumber, ++maxNumber);
        for (inputBag.startOver(); inputBag.hasNext();)
            full.exclude(inputBag.next());
        int missingCount = full.quantity;
        if (missingCount == 0)
            return new int[0];
        Interval[] intervals = new Interval[missingCount];
        intervals[0] = full;
        int[] dividers = new int[missingCount];
        dividers[0] = minNumber;
        int intervalCount = 1;
        while (true) {
            int oldCount = intervalCount;
            for (int i = 0; i < oldCount; i++) {
                Interval itv = intervals[i];
                if (itv.ambiguous)
                    if (itv.quantity == 1) // number inside itv uniquely identified
                        itv.ambiguous = false;
                    else
                        intervalCount++; // itv will be split into two intervals
            }
            if (oldCount == intervalCount)
                break;
            int newIndex = intervalCount - 1;
            int end = maxNumber;
            for (int oldIndex = oldCount - 1; oldIndex >= 0; oldIndex--) {
                // newIndex always >= oldIndex
                Interval itv = intervals[oldIndex];
                int begin = itv.begin;
                if (itv.ambiguous) {
                    // split interval itv
                    // use floorDiv instead of / because input numbers can be negative
                    int mean = (int)Math.floorDiv(itv.sum, itv.quantity) + 1;
                    intervals[newIndex--] = new Interval(mean, end);
                    intervals[newIndex--] = new Interval(begin, mean);
                } else
                    intervals[newIndex--] = itv;
                end = begin;
            }
            for (int i = 0; i < intervalCount; i++)
                dividers[i] = intervals[i].begin;
            for (inputBag.startOver(); inputBag.hasNext();) {
                int x = inputBag.next();
                // find the interval to which x belongs
                int i = java.util.Arrays.binarySearch(dividers, 0, intervalCount, x);
                if (i < 0)
                    i = -i - 2;
                Interval itv = intervals[i];
                if (itv.ambiguous)
                    itv.exclude(x);
            }
        }
        assert intervalCount == missingCount;
        for (int i = 0; i < intervalCount; i++)
            dividers[i] = (int)intervals[i].sum;
        return dividers;
    }
}

공정성을 위해이 클래스는 NumberBag객체 형식으로 입력을받습니다 . NumberBag배열 수정 및 임의 액세스를 허용하지 않으며 순차적 순회에 대해 배열이 요청 된 횟수도 계산합니다. 또한 Iterable<Integer>기본 int값의 복싱을 피하고 int[]편리한 테스트 준비를 위해 큰 부분을 래핑 할 수 있기 때문에 대규모 배열 테스트에 더 적합합니다 . 원하는 경우, 대체 어렵지 않다 NumberBag의해 int[]또는 Iterable<Integer>을 입력 findforeach는 것들로 두에 대한 - 루프를 변경하여 서명.

import java.util.*;

public abstract class NumberBag {
    private int passCount;

    public void startOver() {
        passCount++;
    }

    public final int getPassCount() {
        return passCount;
    }

    public abstract boolean hasNext();

    public abstract int next();

    // A lightweight version of Iterable<Integer> to avoid boxing of int
    public static NumberBag fromArray(int[] base, int fromIndex, int toIndex) {
        return new NumberBag() {
            int index = toIndex;

            public void startOver() {
                super.startOver();
                index = fromIndex;
            }

            public boolean hasNext() {
                return index < toIndex;
            }

            public int next() {
                if (index >= toIndex)
                    throw new NoSuchElementException();
                return base[index++];
            }
        };
    }

    public static NumberBag fromArray(int[] base) {
        return fromArray(base, 0, base.length);
    }

    public static NumberBag fromIterable(Iterable<Integer> base) {
        return new NumberBag() {
            Iterator<Integer> it;

            public void startOver() {
                super.startOver();
                it = base.iterator();
            }

            public boolean hasNext() {
                return it.hasNext();
            }

            public int next() {
                return it.next();
            }
        };
    }
}

테스트

이러한 클래스의 사용법을 보여주는 간단한 예가 아래에 나와 있습니다.

import java.util.*;

public class SimpleTest {
    public static void main(String[] args) {
        int[] input = { 7, 1, 4, 9, 6, 2 };
        NumberBag bag = NumberBag.fromArray(input);
        int[] output = MissingNumbers.find(1, 10, bag);
        System.out.format("Input: %s%nMissing numbers: %s%nPass count: %d%n",
                Arrays.toString(input), Arrays.toString(output), bag.getPassCount());

        List<Integer> inputList = new ArrayList<>();
        for (int i = 0; i < 10; i++)
            inputList.add(2 * i);
        Collections.shuffle(inputList);
        bag = NumberBag.fromIterable(inputList);
        output = MissingNumbers.find(0, 19, bag);
        System.out.format("%nInput: %s%nMissing numbers: %s%nPass count: %d%n",
                inputList, Arrays.toString(output), bag.getPassCount());

        // Sieve of Eratosthenes
        final int MAXN = 1_000;
        List<Integer> nonPrimes = new ArrayList<>();
        nonPrimes.add(1);
        int[] primes;
        int lastPrimeIndex = 0;
        while (true) {
            primes = MissingNumbers.find(1, MAXN, NumberBag.fromIterable(nonPrimes));
            int p = primes[lastPrimeIndex]; // guaranteed to be prime
            int q = p;
            for (int i = lastPrimeIndex++; i < primes.length; i++) {
                q = primes[i]; // not necessarily prime
                int pq = p * q;
                if (pq > MAXN)
                    break;
                nonPrimes.add(pq);
            }
            if (q == p)
                break;
        }
        System.out.format("%nSieve of Eratosthenes. %d primes up to %d found:%n",
                primes.length, MAXN);
        for (int i = 0; i < primes.length; i++)
            System.out.format(" %4d%s", primes[i], (i % 10) < 9 ? "" : "\n");
    }
}

다음과 같이 대규모 어레이 테스트를 수행 할 수 있습니다.

import java.util.*;

public class BatchTest {
    private static final Random rand = new Random();
    public static int MIN_NUMBER = 1;
    private final int minNumber = MIN_NUMBER;
    private final int numberCount;
    private final int[] numbers;
    private int missingCount;
    public long finderTime;

    public BatchTest(int numberCount) {
        this.numberCount = numberCount;
        numbers = new int[numberCount];
        for (int i = 0; i < numberCount; i++)
            numbers[i] = minNumber + i;
    }

    private int passBound() {
        int mBound = missingCount > 0 ? missingCount : 1;
        int nBound = 34 - Integer.numberOfLeadingZeros(numberCount - 1); // ceil(log_2(numberCount)) + 2
        return Math.min(mBound, nBound);
    }

    private void error(String cause) {
        throw new RuntimeException("Error on '" + missingCount + " from " + numberCount + "' test, " + cause);
    }

    // returns the number of times the input array was traversed in this test
    public int makeTest(int missingCount) {
        this.missingCount = missingCount;
        // numbers array is reused when numberCount stays the same,
        // just Fisher–Yates shuffle it for each test
        for (int i = numberCount - 1; i > 0; i--) {
            int j = rand.nextInt(i + 1);
            if (i != j) {
                int t = numbers[i];
                numbers[i] = numbers[j];
                numbers[j] = t;
            }
        }
        final int bagSize = numberCount - missingCount;
        NumberBag inputBag = NumberBag.fromArray(numbers, 0, bagSize);
        finderTime -= System.nanoTime();
        int[] found = MissingNumbers.find(minNumber, minNumber + numberCount - 1, inputBag);
        finderTime += System.nanoTime();
        if (inputBag.getPassCount() > passBound())
            error("too many passes (" + inputBag.getPassCount() + " while only " + passBound() + " allowed)");
        if (found.length != missingCount)
            error("wrong result length");
        int j = bagSize; // "missing" part beginning in numbers
        Arrays.sort(numbers, bagSize, numberCount);
        for (int i = 0; i < missingCount; i++)
            if (found[i] != numbers[j++])
                error("wrong result array, " + i + "-th element differs");
        return inputBag.getPassCount();
    }

    public static void strideCheck(int numberCount, int minMissing, int maxMissing, int step, int repeats) {
        BatchTest t = new BatchTest(numberCount);
        System.out.println("╠═══════════════════════╬═════════════════╬═════════════════╣");
        for (int missingCount = minMissing; missingCount <= maxMissing; missingCount += step) {
            int minPass = Integer.MAX_VALUE;
            int passSum = 0;
            int maxPass = 0;
            t.finderTime = 0;
            for (int j = 1; j <= repeats; j++) {
                int pCount = t.makeTest(missingCount);
                if (pCount < minPass)
                    minPass = pCount;
                passSum += pCount;
                if (pCount > maxPass)
                    maxPass = pCount;
            }
            System.out.format("║ %9d  %9d  ║  %2d  %5.2f  %2d  ║  %11.3f    ║%n", missingCount, numberCount, minPass,
                    (double)passSum / repeats, maxPass, t.finderTime * 1e-6 / repeats);
        }
    }

    public static void main(String[] args) {
        System.out.println("╔═══════════════════════╦═════════════════╦═════════════════╗");
        System.out.println("║      Number count     ║      Passes     ║  Average time   ║");
        System.out.println("║   missimg     total   ║  min  avg   max ║ per search (ms) ║");
        long time = System.nanoTime();
        strideCheck(100, 0, 100, 1, 20_000);
        strideCheck(100_000, 2, 99_998, 1_282, 15);
        MIN_NUMBER = -2_000_000_000;
        strideCheck(300_000_000, 1, 10, 1, 1);
        time = System.nanoTime() - time;
        System.out.println("╚═══════════════════════╩═════════════════╩═════════════════╝");
        System.out.format("%nSuccess. Total time: %.2f s.%n", time * 1e-9);
    }
}

Ideone에서 사용해보십시오


0

임의의 큰 정수에 대한 및 함수를 사용할 수 있다고 가정하면 O(k)시간 및 O(log(k))공간 알고리즘 이 있다고 생각 합니다.floor(x)log2(x)

k- log8(k)를 추가 하는 -비트 long 정수 (따라서 공백)가 있습니다 x^2. 여기서 x는 백에서 찾은 다음 숫자입니다. 시간 s=1^2+2^2+...이 걸립니다 O(N)(면접관에게는 문제가되지 않습니다). 결국 당신은 j=floor(log2(s))당신이 찾고있는 가장 큰 숫자 를 얻 습니다. 그런 다음 s=s-j위의 작업을 다시 수행하십시오.

for (i = 0 ; i < k ; i++)
{
  j = floor(log2(s));
  missing[i] = j;
  s -= j;
}

이제 일반적으로 2756-비트 정수에 대한 floor 및 log2 함수가 없지만 대신 double이 있습니다. 그래서? 간단히 말하면, 각 2 바이트 (또는 1, 3 또는 4)에 대해이 함수를 사용하여 원하는 숫자를 얻을 수 있지만 O(N)시간 복잡성에 요인 이 추가됩니다.


0

이것은 어리석게 들릴지 모르지만 첫 번째 문제에서 가방에 남아있는 모든 숫자를 확인하여 실제로 그 방정식을 사용하여 누락 된 숫자를 찾기 위해 숫자를 더해야합니다.

따라서 모든 숫자를 볼 수 있으므로 빠진 숫자를 찾으십시오. 두 개의 숫자가 누락 된 경우에도 마찬가지입니다. 아주 간단하다고 생각합니다. 가방에 남아있는 숫자를 볼 때 방정식 사용에 아무런 의미가 없습니다.


2
그것들을 요약하면 이미 어떤 숫자를 보았는지 기억할 필요가 없다는 것입니다 (예 : 추가 메모리 요구 사항 없음). 그렇지 않으면 유일한 옵션은 표시된 모든 값 집합을 유지 한 다음 해당 집합을 다시 반복하여 누락 된 값을 찾는 것입니다.
Dan Tao

3
이 질문은 일반적으로 O (1) 공간 복잡성의 규정과 함께 묻습니다.

첫 번째 N 수의 합은 N (N + 1) / 2입니다. N = 100 인 경우, Sum = 100 * (101) / 2 = 5050;
tmarthal

0

나는 이것이 다음과 같이 일반화 될 수 있다고 생각한다.

산술 시리즈와 곱셈의 합의 초기 값으로 S, M을 표시하십시오.

S = 1 + 2 + 3 + 4 + ... n=(n+1)*n/2
M = 1 * 2 * 3 * 4 * .... * n 

나는 이것을 계산하는 공식에 대해 생각해야하지만, 요점이 아닙니다. 어쨌든 하나의 숫자가 없으면 이미 솔루션을 제공 한 것입니다. 그러나 두 개의 숫자가 누락 된 경우 S1과 M1에 의해 새 합계와 총 배수를 나타내면 다음과 같습니다.

S1 = S - (a + b)....................(1)

Where a and b are the missing numbers.

M1 = M - (a * b)....................(2)

S1, M1, M 및 S를 알고 있으므로 위의 방정식은 결 측값 a와 b를 찾을 수 있습니다.

이제 세 개의 숫자가 누락되었습니다.

S2 = S - ( a + b + c)....................(1)

Where a and b are the missing numbers.

M2 = M - (a * b * c)....................(2)

이제 당신이 알 수없는 것은 3이며, 당신은 해결할 수있는 두 가지 방정식을 가지고 있습니다.


곱셈은 ​​상당히 커집니다. 또한 2 개 이상의 누락 된 숫자로 일반화하는 방법은 무엇입니까?
Thomas Ahle

N = 3이고 누락 된 숫자 = {1, 2} 인 매우 간단한 순서로 이러한 수식을 시도했습니다. 오류가 수식 (2)에 있다고 생각하기 때문에 작동하지 않았습니다 M1 = M / (a * b)( 해답 참조 ). 그런 다음 잘 작동합니다.
dma_k

0

이것이 효과적인지 아닌지는 모르겠지만이 솔루션을 제안하고 싶습니다.

  1. 100 개 요소의 계산 xor
  2. 98 개 요소의 xor 계산 (2 개 요소를 제거한 후)
  3. 이제 (1의 결과) XOR (2의 결과)은 a와 b가 누락 된 요소 인 경우 두 개의 누락 된 숫자, 즉 XOR b의 xor를 제공합니다 .4
    일반적인 방법으로 누락 된 숫자의 합계를 얻 습니다. 수식 diff를 합산하고 diff가 d라고 말합니다.

이제 루프를 실행하여 가능한 쌍 (p, q)을 [1, 100]에 있고 d를 합산하십시오.

쌍이 얻어지면 (결과 3) XOR p = q인지 확인하고 그렇다면 그렇다면 우리는 완료됩니다.

내가 틀렸다면 저를 정정하고 이것이 정확하다면 시간 복잡성에 대해서도 언급하십시오.


2
합계와 xor가 두 숫자를 고유하게 정의한다고 생각하지 않습니다. d에 합산 가능한 모든 k- 튜플을 얻기 위해 루프를 실행하면 시간 O (C (n, k-1)) = O (n <sup> k-1 </ sup>)가 걸리고 k> 2 인 경우 나쁘다.
Teepeemm

0

우리는 대부분 O (log n) 에서 Q1과 Q2 를 할 수 있습니다 .

우리 가정 memory chip의 배열로 구성되어 ntest tubes. x테스트 튜브 의 숫자 x milliliter는 화학 액체로 표시됩니다 .

우리의 프로세서가 가정하자 laser light. 레이저를 비추면 레이저가 길이에 수직으로 모든 튜브를 가로지 릅니다. 화학 액체를 통과 할 때마다 광도가 감소합니다 1. 그리고 특정 밀리리터 표시로 빛을 통과시키는 것은의 작업입니다 O(1).

이제 테스트 튜브의 중간에 레이저를 비추고 광도의 출력을 얻는다면

  • 미리 계산 된 값 (숫자가없는 경우 계산)과 같으면 누락 된 숫자가보다 큽니다 n/2.
  • 출력이 작은 경우보다 작은 하나 이상의 누락 된 숫자가 n/2있습니다. 광도가 1또는 로 감소하는지 확인할 수도 있습니다 2. 감소 1하면 하나의 누락 된 숫자는보다 작고 n/2다른 하나는보다 큽니다 n/2. 감소 2하면 두 숫자가 모두보다 작습니다 n/2.

위의 과정을 반복하여 문제 영역을 좁힐 수 있습니다. 각 단계에서 도메인을 반으로 작게 만듭니다. 그리고 마지막으로 결과를 얻을 수 있습니다.

언급 할만한 가치가있는 병렬 알고리즘 (흥미 롭기 때문에)

  • 예를 들어 병렬 병합은 O(log^3 n)시간 에 따라 수행 될 수 있습니다 . 그리고 누락 된 숫자는 O(log n)시간 에 따라 이진 검색으로 찾을 수 있습니다 .
  • 이론적으로 n프로세서 가 있으면 각 프로세스는 입력 중 하나를 확인하고 숫자를 식별하는 플래그를 설정합니다 (편리하게 배열로 표시). 그리고 다음 단계에서 각 프로세스는 각 플래그를 확인하고 마지막으로 플래그가 지정되지 않은 숫자를 출력 할 수 있습니다. 전체 과정에는 O(1)시간 이 걸립니다 . 추가 O(n)공간 / 메모리 요구 사항이 있습니다.

참고 것을 주석에서 설명한 바와 같이 상기 제공된 두 개의 병렬 알고리즘의 추가 공간을 필요로 할 수있다 .


테스트 튜브 레이저 방법은 정말 흥미롭지 만 하드웨어 명령어로 잘 번역되지 않아 O(logn)컴퓨터에 있을 가능성은 거의 없다는 데 동의 합니다.
SirGuy

1
정렬 방법에 대해서는 시간 NO(N)지남 에 따라 더 많은 공간 이 필요합니다 (의 종속성 측면에서 N).
SirGuy

@SirGuy 테스트 튜브 개념과 병렬 처리 메모리 비용에 대한 귀하의 관심에 감사드립니다. 내 게시물은 문제에 대한 내 생각을 공유하는 것입니다. GPU 프로세서는 현재 병렬 처리를 수행하고 있습니다. 테스트 튜브 개념을 향후에 사용할 수 없다면 누가 알겠습니까?
shuva
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.