주어진 정렬되지 않은 정수 목록에 존재하지 않는 첫 번째 (가장 작은) 정수를 찾는 가장 빠른 방법은 무엇입니까 ?
내 기본 접근 방식은 정렬하고 목록을 단계별로 실행하는 것입니다. 더 좋은 방법이 있습니까?
If you have a question about… •algorithm and data structure concepts
에서 주제 IMHO에 있습니다.
주어진 정렬되지 않은 정수 목록에 존재하지 않는 첫 번째 (가장 작은) 정수를 찾는 가장 빠른 방법은 무엇입니까 ?
내 기본 접근 방식은 정렬하고 목록을 단계별로 실행하는 것입니다. 더 좋은 방법이 있습니까?
If you have a question about… •algorithm and data structure concepts
에서 주제 IMHO에 있습니다.
답변:
"숫자"라고 말할 때 "정수"를 의미한다고 가정하면 크기가 2 ^ n 인 비트 벡터를 사용할 수 있습니다. 여기서 n은 요소의 수입니다 (예 : 범위는 1과 256 사이의 정수를 포함하므로 256- 비트 또는 32 바이트, 비트 벡터). 범위의 n 위치에서 정수를 만나면 n 번째 비트를 설정하십시오.
정수 컬렉션 열거가 끝나면 비트 벡터의 비트를 반복하여 비트 세트 0의 위치를 찾습니다. 이제 누락 된 정수의 위치 n과 일치합니다.
이것은 O (2 * N)이므로 O (N)이며 전체 목록을 정렬하는 것보다 메모리 효율성이 높습니다.
"구멍"이 하나 뿐인 배열의 특수한 경우에는 난해하고 "영리한"XOR 기반 솔루션을 사용해 볼 수 있습니다.
이것은 비트 벡터 솔루션과 비슷한 약 2N 시간에 실행되지만 N> sizeof (int)에 대해 더 적은 메모리 공간이 필요합니다. 그러나 배열에 "구멍"이 여러 개 있으면 X는 모든 구멍의 XOR "합"이되어 실제 구멍 값으로 분리하기 어렵거나 불가능합니다. 이 경우 다른 답변의 "피벗"또는 "비트 벡터"접근과 같은 다른 방법으로 넘어갑니다.
복잡성을 더 줄이기 위해 피벗 방법과 유사한 것을 사용하여 이것을 재귀 할 수도 있습니다. 피벗 점을 기준으로 배열을 다시 정렬합니다 (왼쪽 최대 값과 오른쪽 최소값이됩니다. 피벗하는 동안 전체 배열의 최대 값과 최소값을 찾는 것이 쉽지 않습니다). 피벗의 왼쪽에 하나 이상의 구멍이 있으면 해당 측면으로 만 반복하십시오. 그렇지 않으면 반대쪽으로 재귀합니다. 구멍이 하나만 있다고 판단 할 수있는 지점에서 XOR 방법을 사용하여 구멍을 찾으십시오 (공구가 알려진 두 개의 요소 모음으로 계속 피벗하는 것보다 전체적으로 저렴해야 함). 순수한 피벗 알고리즘).
당신이 보게 될 숫자의 범위는 무엇입니까? 해당 범위가 매우 크지 않은 경우 숫자가있는 요소가 많은 배열을 사용하여 두 번의 스캔 (선형 시간 O (n))으로이 문제를 해결할 수 있습니다. 한 번 더 스캔하여 범위를 동적으로 찾을 수 있습니다. 공간을 줄이기 위해 각 숫자에 1 비트를 할당하여 바이트 당 8 개의 숫자 스토리지를 제공 할 수 있습니다.
초기 시나리오에서 더 좋으며 메모리를 복사하는 대신 원치 않는 다른 옵션은 검색 단계에서 찾은 최소값이 마지막으로 찾은 최소값보다 1이 아닌 경우 선택 정렬을 수정하여 일찍 종료하는 것입니다.
아니 정말. 아직 스캔되지 않은 숫자는 항상 주어진 "구멍"을 채우는 숫자 일 수 있으므로 각 숫자를 한 번 이상 스캔 한 다음 가능한 이웃과 비교하는 것을 피할 수 없습니다. 바이너리 트리를 만들거나 구멍을 찾을 때까지 왼쪽에서 오른쪽으로 순회하여 속도를 높일 수는 있지만 정렬하는 것과 본질적으로 정렬과 시간이 복잡합니다. 그리고 당신은 아마도 Timsort 보다 더 빠른 것을 내놓지 않을 것 입니다.
여기에있는 대부분의 아이디어는 단순한 분류에 지나지 않습니다. 비트 벡터 버전은 일반 Bucketsort입니다. 힙 정렬도 언급되었습니다. 기본적으로 시간 / 공간 요구 사항과 요소의 범위 및 수에 따라 올바른 정렬 알고리즘을 선택합니다.
필자의 관점에서 힙 구조를 사용하는 것이 가장 일반적인 솔루션 일 것입니다 (힙은 기본적으로 완전한 정렬없이 가장 작은 요소를 효율적으로 제공합니다).
가장 작은 숫자를 먼저 찾은 다음 그보다 큰 각 정수를 스캔하는 접근법을 분석 할 수도 있습니다. 또는 의지에 차이가 있기를 희망하는 5 개의 가장 작은 숫자를 찾으십시오.
이러한 모든 알고리즘은 입력 특성 및 프로그램 요구 사항에 따라 그 강도가 높습니다.
추가 스토리지를 사용하지 않거나 정수의 너비 (32 비트)를 가정하는 솔루션입니다.
하나의 선형 패스에서 가장 작은 숫자를 찾으십시오. 이것을 "min"이라고하자. O (n) 시간 복잡성.
임의의 피벗 요소를 선택하고 퀵 정렬 스타일 파티션을 수행하십시오.
피벗이 = ( "피벗"- "최소") 위치에 있으면 파티션의 오른쪽에서 되풀이되고, 그렇지 않으면 파티션의 왼쪽에서 되풀이됩니다. 여기서 아이디어는 처음부터 구멍이 없으면 피벗이 ( "피벗"- "최소") 위치에있게되므로 첫 번째 구멍은 파티션의 오른쪽에 있어야하고 그 반대도 마찬가지입니다.
기본 사례는 1 요소의 배열이며이 요소와 다음 요소 사이에 구멍이 있습니다.
예상되는 총 실행 시간 복잡도는 O (n) (상수의 경우 8 * n)이고 최악의 경우는 O (n ^ 2)입니다. 비슷한 문제에 대한 시간 복잡도 분석은 여기 에서 찾을 수 있습니다 .
나는 중복이 없다고 보장되면 일반적으로 효율적으로 작동 해야하는 무언가를 생각해 냈습니다 * (그러나 구멍의 수와 범위의 정수로 확장 할 수 있어야 함).
이 방법의 배후에있는 아이디어는 퀵 정렬과 비슷합니다. 피벗과 그 주변을 분할 한 다음 구멍이있는 측면에서 재귀합니다. 어느쪽에 구멍이 있는지 확인하기 위해 가장 낮은 숫자와 가장 높은 숫자를 찾아서 그 쪽의 피벗 및 값 수와 비교합니다. 피벗이 17이고 최소 수가 11이라고 가정합니다. 구멍이 없으면 6 개의 숫자 (11, 12, 13, 14, 15, 16, 17)가 있어야합니다. 5가 있다면, 우리는 그쪽에 구멍이 있다는 것을 알고 우리는 그 쪽에서 재귀하여 그것을 찾을 수 있습니다. 그보다 더 명확하게 설명하는 데 어려움이 있으므로 예를 들어 보겠습니다.
15 21 10 13 18 16 22 23 24 20 17 11 25 12 14
피벗:
10 13 11 12 14 |15| 21 18 16 22 23 24 20 17 25
15는 파이프 ( ||
)로 표시된 피벗 입니다. 피벗의 왼쪽에는 5 개의 숫자가 있으며, 오른쪽에는 (15-10), 오른쪽에는 9가 있어야합니다 (10 (25-15)). 그래서 우리는 오른쪽에 재귀합니다. 구멍이 인접 해있을 경우 이전 경계는 15라는 점에 유의하십시오 (16).
[15] 18 16 17 20 |21| 22 23 24 25
왼쪽에는 4 개의 숫자가 있지만 5 (21-16)가 있어야합니다. 그래서 우리는 그곳에서 되풀이하고 다시 괄호 안의 이전 경계를 주목할 것입니다.
[15] 16 17 |18| 20 [21]
왼쪽에는 올바른 2 개의 숫자 (18-16)가 있지만 오른쪽에는 2 대신에 1 (20-18)이 있습니다. 우리의 결말 조건에 따라 1 수를 양변 (18, 20)과 비교하고 19가 한 번 더 누락되거나 재귀하는 것을 볼 수 있습니다.
[18] |20| [21]
왼쪽의 크기는 0이며 피벗 (20)과 이전 경계 (18) 사이에 간격이 있으므로 19는 구멍입니다.
* : 중복이있는 경우 전체 메소드 O (N)을 유지하면서 해시 세트를 사용하여 O (N) 시간에 제거 할 수 있지만 다른 메소드를 사용하는 것보다 시간이 더 걸릴 수 있습니다.