입력 : 0에서 n-1까지의 요소를 포함하는 n 요소의 배열이 주어지며,이 숫자 중 임의의 횟수가 나타납니다.
목표 : O (n)에서 이러한 반복되는 숫자를 찾고 일정한 메모리 공간 만 사용합니다.
예를 들어, n이 7이고 배열이 {1, 2, 3, 1, 3, 0, 6}이라고합시다. 대답은 1 & 3이되어야합니다. 여기에서 비슷한 질문을 확인했지만 대답은 HashSet
기타 와 같은 일부 데이터 구조를 사용했습니다 .
동일한 효율적인 알고리즘이 있습니까?
답변
이것은 추가 부호 비트가 필요하지 않은 내가 생각해 낸 것입니다.
for i := 0 to n - 1
while A[A[i]] != A[i]
swap(A[i], A[A[i]])
end while
end for
for i := 0 to n - 1
if A[i] != i then
print A[i]
end if
end for
첫 번째 루프 x
는 요소 가 한 번 이상 존재하면 해당 항목 중 하나가 위치에 있도록 배열을 순열합니다 A[x]
.
처음에는 O (n)처럼 보이지 않을 수도 있지만, 중첩 된 루프가 있어도 여전히 제 O(N)
시간에 실행됩니다 . 스왑은 i
that 이있는 경우에만 발생 A[i] != i
하며 각 스왑은 A[i] == i
이전에 true가 아니었던 that 과 같은 요소를 하나 이상 설정합니다 . 즉, 총 스왑 수 (따라서 while
루프 본문 의 총 실행 수 )가 최대 N-1
.
두 번째 루프는 x
for가 A[x]
같지 않은 값을 인쇄합니다 x
. 첫 번째 루프 x
는 배열에 적어도 한 번 존재 하는 경우 해당 인스턴스 중 하나가에 있음을 보장하기 때문에 해당 인스턴스에 존재하지 않는 A[x]
값을 인쇄합니다 x
. 배열.
답변
caf의 훌륭한 대답 은 배열에 k 번 나타나는 각 숫자를 k-1 번 인쇄합니다. 이것은 유용한 동작이지만 질문은 각 복제물이 한 번만 인쇄되도록 요구하며 선형 시간 / 상수 공간 경계를 날리지 않고이를 수행 할 수있는 가능성을 암시합니다. 이것은 그의 두 번째 루프를 다음 의사 코드로 대체하여 수행 할 수 있습니다.
for (i = 0; i < N; ++i) {
if (A[i] != i && A[A[i]] == A[i]) {
print A[i];
A[A[i]] = i;
}
}
이것은 첫 번째 루프가 실행 된 후 값 m
이 두 번 이상 나타나는 경우 해당 모양 중 하나가 올바른 위치에 있음을 보장 하는 속성을 이용합니다 A[m]
. 주의를 기울이면 해당 “홈”위치를 사용하여 사본이 아직 인쇄되었는지 여부에 대한 정보를 저장할 수 있습니다.
caf의 버전에서 배열을 살펴 A[i] != i
보았을 때 그것이 A[i]
중복 임을 암시했습니다 . 내 버전에서는 약간 다른 불변에 의존합니다. A[i] != i && A[A[i]] == A[i]
이는 이전에 본 적이없는A[i]
중복 임을 의미합니다 . ( “우리가 이전에 본 적이없는”부분을 삭제하면 나머지는 caf의 불변의 진실과 모든 복제본이 홈 위치에 일부 사본이 있다는 보장에 의해 암시되는 것으로 볼 수 있습니다.)이 속성은 다음과 같습니다. 처음 (카페의 첫 번째 루프가 끝난 후)와 각 단계 후에 유지된다는 것을 아래에 보여줍니다.
배열을 살펴보면 A[i] != i
테스트 부분의 성공은 이전에 본 적이없는 중복 일 A[i]
수 있음을 의미합니다 . 이전에 본 적이 없다면 A[i]
의 집 위치가 자신을 가리킬 것으로 예상 if
합니다. 이것이 조건의 후반부에서 테스트 한 것입니다. 이 경우 인쇄하고 홈 위치를 변경하여 처음 발견 된이 복제본을 다시 가리 키도록하여 2 단계 “주기”를 만듭니다.
이 작업은 우리의 불변을 변경할 생각하지 않는 것을 확인하려면 m = A[i]
특정 위치에 대한 i
만족 A[i] != i && A[A[i]] == A[i]
. 우리가 만든 변경 사항 ( A[A[i]] = i
)은 조건의 m
후반부 if
를 실패하게하여 다른 집이 아닌 항목이 중복으로 출력되는 것을 방지하기 위해 작동 하지만 i
, 집 위치에 도착하면 작동 m
할까요? 예, 이제이 새로운 조건 i
에서 if
조건의 전반부 A[i] != i
가 참인 것을 발견 하더라도 후반부는 가리키는 위치가 집 위치인지 아닌지 여부를 테스트하기 때문입니다. 이 상황에서 우리는 더 이상 여부를 알 수 없습니다 m
또는 A[m]
중복 값이었다, 그러나 우리는 어느 쪽이든 것을 알고,이 2주기가 caf의 첫 번째 루프의 결과에 나타나지 않도록 보장되기 때문에 이미보고되었습니다 . ( m != A[m]
그런 다음 정확히 하나의 m
and A[m]
가 두 번 이상 발생하고 다른 하나는 전혀 발생하지 않습니다.)
답변
다음은 의사 코드입니다.
for i <- 0 to n-1:
if (A[abs(A[i])]) >= 0 :
(A[abs(A[i])]) = -(A[abs(A[i])])
else
print i
end for
답변
상대적으로 작은 N의 경우 div / mod 작업을 사용할 수 있습니다.
n.times do |i|
e = a[i]%n
a[e] += n
end
n.times do |i|
count = a[i]/n
puts i if count > 1
end
C / C ++는 아니지만 어쨌든
답변
정말 예쁘지는 않지만 적어도 O (N) 및 O (1) 속성을 쉽게 볼 수 있습니다. 기본적으로 배열을 스캔하고 각 숫자에 대해 해당 위치가 이미 본 한 번 (N) 또는 이미 본 여러 번 (N + 1) 플래그가 지정되었는지 확인합니다. 이미 본 한 번 플래그가 지정된 경우이를 인쇄하고 이미 본 여러 번 플래그를 지정합니다. 플래그가 지정되지 않은 경우 이미 본 한 번 플래그를 지정하고 해당 인덱스의 원래 값을 현재 위치로 이동합니다 (플래 깅은 파괴적인 작업입니다).
for (i=0; i<a.length; i++) {
value = a[i];
if (value >= N)
continue;
if (a[value] == N) {
a[value] = N+1;
print value;
} else if (a[value] < N) {
if (value > i)
a[i--] = a[value];
a[value] = N;
}
}
또는 더 나은 방법 (이중 루프에도 불구하고 더 빠름) :
for (i=0; i<a.length; i++) {
value = a[i];
while (value < N) {
if (a[value] == N) {
a[value] = N+1;
print value;
value = N;
} else if (a[value] < N) {
newvalue = value > i ? a[value] : N;
a[value] = N;
value = newvalue;
}
}
}
답변
C의 한 가지 해결책은 다음과 같습니다.
#include <stdio.h>
int finddup(int *arr,int len)
{
int i;
printf("Duplicate Elements ::");
for(i = 0; i < len; i++)
{
if(arr[abs(arr[i])] > 0)
arr[abs(arr[i])] = -arr[abs(arr[i])];
else if(arr[abs(arr[i])] == 0)
{
arr[abs(arr[i])] = - len ;
}
else
printf("%d ", abs(arr[i]));
}
}
int main()
{
int arr1[]={0,1,1,2,2,0,2,0,0,5};
finddup(arr1,sizeof(arr1)/sizeof(arr1[0]));
return 0;
}
O (n) 시간과 O (1) 공간 복잡성입니다.
답변
이 배열을 단방향 그래프 데이터 구조로 제시한다고 가정 해 보겠습니다. 각 숫자는 꼭지점이고 배열의 인덱스는 그래프의 가장자리를 형성하는 다른 꼭지점을 가리 킵니다.
더 간단하게하기 위해 0에서 n-1까지의 인덱스와 0..n-1의 숫자 범위가 있습니다. 예 :
0 1 2 3 4
a[3, 2, 4, 3, 1]
0 (3)-> 3 (3)은주기입니다.
답변 : 인덱스를 사용하여 배열을 순회하십시오. a [x] = a [y]이면 순환이므로 중복됩니다. 다음 인덱스로 건너 뛰고 배열이 끝날 때까지 계속합니다. 복잡성 : O (n) 시간 및 O (1) 공간.