알고리즘 : 배열에서 중복 정수를 제거하는 효율적인 방법


92

Microsoft와의 인터뷰에서이 문제가 발생했습니다.

임의의 정수 배열이 주어지면 중복 된 숫자를 제거하고 원래 배열의 고유 한 숫자를 반환하는 알고리즘을 C로 작성합니다.

예 : 입력 : {4, 8, 4, 1, 1, 2, 9} 출력 :{4, 8, 1, 2, 9, ?, ?}

한 가지주의 할 점은 예상 알고리즘이 배열을 먼저 정렬 할 필요가 없다는 것입니다. 요소가 제거되면 다음 요소도 앞으로 이동해야합니다. 어쨌든, 요소가 앞으로 이동 한 배열의 꼬리에있는 요소의 값은 무시할 수 있습니다.

업데이트 : 결과는 원래 배열로 반환되어야하며 도우미 데이터 구조 (예 : 해시 테이블)를 사용해서는 안됩니다. 그러나 주문 보존은 필요하지 않은 것 같습니다.

업데이트 2 : 왜 이러한 비실용적 인 제약이 있는지 궁금해하는 사람들을 위해 이것은 인터뷰 질문이었고 이러한 모든 제약은 제가 다른 아이디어를 어떻게 생각 해낼 수 있는지보기 위해 사고 과정에서 논의됩니다.


4
고유 번호의 순서를 유지해야합니까?
Douglas Leeder

1
결과를 원래 배열로 반환해야합니까?
Douglas Leeder

1
질문을 업데이트했습니다. 결과는 원래 배열로 반환되어야합니다. 그러나 시퀀스의 순서는 중요하지 않습니다.
ejel

3
누군가가 질문과 다른 답변에 대한 답을 포주하면 꽤 짜증납니다. 인내심을 가지십시오. 사람들이 거기에 도착할 것입니다.
GManNickG

2
해시 테이블이 허용되지 않는 이유는 무엇입니까? 그 제한은 말이되지 않습니다.
RBarryYoung

답변:


19

어때 :

void rmdup(int *array, int length)
{
    int *current , *end = array + length - 1;

    for ( current = array + 1; array < end; array++, current = array + 1 )
    {
        while ( current <= end )
        {
            if ( *current == *array )
            {
                *current = *end--;
            }
            else
            {
                current++;
            }
        }
    }
}

O (n ^ 2) 이하 여야합니다.


3
이것은 간단한 해결책이며 인터뷰 질문이 찾고있는 것보다 더 많습니다.
Kirk Broadhurst

7
그들은 당신에게 런타임 제약을주지 않는 한 당신이 조기 최적화에 빠져들지 않는지 확인하고있을 수도 있습니다! :-)
Trevor Tippins

16
Lol, 배열을 정렬하고 정렬 된 배열에서 작업하는 것이 확실히 더 빠릅니다. 정렬은 API에 의해 제공되어야하며 조기 최적화가 아닙니다.
ziggystar 2009

2
while (current <end) 대신 while (current <= end)이어야하지 않습니까?
Shail

2
이것이 정답으로 받아 들여진 이유는 무엇입니까? 순서 보존이 필요하지 않은 경우 병합 정렬 O (nlogn)을 사용한 다음 O (n)에서 반복되는 요소를 제거하는 것이 좋지 않습니다. 총 복잡성-O (nlogn)이 솔루션보다 훨씬 낫습니다.
Pawan

136

내 여자 친구가 제안한 해결책은 병합 정렬의 변형입니다. 유일한 수정은 병합 단계에서 중복 된 값을 무시하는 것입니다. 이 솔루션은 O (n log n)입니다. 이 접근 방식에서는 정렬 / 중복 제거가 함께 결합됩니다. 그러나 그것이 어떤 차이를 만드는지 확실하지 않습니다.


8
좋은 제안이지만 각 병합 출력의 끝을 추적하려면 몇 가지 부기가 필요합니다. 나는 실제로 이것을 한 번 수행했으며 네가 병합 할 때 중복을 제거하면 훨씬 빨라집니다.
Mark Ransom

2
O (N / 2) 추가 공간이 질문에서 금지 된 "도우미 데이터 구조"로 간주되는지 여부는 명확하지 않습니다. 제한이 O (1) 추가 공간을 규정하기위한 것인지 아니면 단순히 대답은 큰 ol '데이터 구조 구현에 의존해서는 안됩니다. 표준 병합이 좋을 수도 있습니다. 하지만 그렇지 않다면 최고의 팁 : 당신이하고있는 일 을 정말로 알지 못한다면 인터뷰에서 인플레 이스 병합 정렬을 쓰려고하지 마십시오 .
Steve Jessop

좋은 생각입니다. 그러나 나머지 데이터는 원래 순서를 유지해야합니다.
Hardy Feng

4
당신의 여자 친구가 무엇을 제안 설명하는 논문은 다음과 같습니다 dc-pubs.dbs.uni-leipzig.de/files/...
마이크 B

50

예전에 한번 올렸는데 꽤 멋있기 때문에 여기서 재현하겠습니다. 해싱을 사용하여 제자리에 설정된 해시와 같은 것을 만듭니다. 겨드랑이 공간에서 O (1)이 보장되며 (재귀는 꼬리 호출 임) 일반적으로 O (N) 시간 복잡도입니다. 알고리즘은 다음과 같습니다.

  1. 배열의 첫 번째 요소를 가져 오면 이것이 센티널이됩니다.
  2. 각 요소가 해시에 해당하는 위치에 있도록 가능한 한 나머지 배열의 순서를 변경하십시오. 이 단계가 완료되면 중복 항목이 발견됩니다. 센티넬과 동일하게 설정하십시오.
  3. 인덱스가 해시와 동일한 모든 요소를 ​​배열의 시작 부분으로 이동합니다.
  4. 배열의 첫 번째 요소를 제외하고 sentinel과 동일한 모든 요소를 ​​배열의 끝으로 이동합니다.
  5. 올바르게 해시 된 요소와 중복 요소 사이에 남는 것은 충돌로 인해 해시에 해당하는 인덱스에 배치 할 수없는 요소입니다. 이러한 요소를 처리하려면 재귀하십시오.

이것은 해싱에 병리학 적 시나리오가없는 경우 O (N)으로 표시 될 수 있습니다. 중복 항목이 없더라도 각 재귀에서 요소의 약 2/3가 제거됩니다. 각 재귀 수준은 O (n)이며 small n은 남은 요소의 양입니다. 유일한 문제는 실제로 중복이 거의 없을 때 즉, 많은 충돌이있을 때 빠른 정렬보다 느리다는 것입니다. 그러나 엄청난 양의 중복이있을 때는 놀랍도록 빠릅니다.

편집 : D의 현재 구현에서 hash_t는 32 비트입니다. 이 알고리즘에 대한 모든 것은 전체 32 비트 공간에서 해시 충돌이 거의 없다고 가정합니다. 그러나 충돌은 모듈러스 공간에서 자주 발생할 수 있습니다. 그러나이 가정은 합리적인 크기의 데이터 세트에 대해 모두 사실입니다. 키가 32 비트보다 작거나 같으면 자체 해시가 될 수 있으므로 전체 32 비트 공간에서 충돌이 불가능합니다. 더 크면 문제가 될 수있는 32 비트 메모리 주소 공간에 충분히 들어갈 수 없습니다. 저는 D의 64 비트 구현에서 hash_t가 64 비트로 증가 할 것이라고 가정합니다. 여기서 데이터 세트는 더 클 수 있습니다. 또한 이것이 문제가된다면 각 재귀 수준에서 해시 함수를 변경할 수 있습니다.

다음은 D 프로그래밍 언어로 구현 된 것입니다.

void uniqueInPlace(T)(ref T[] dataIn) {
    uniqueInPlaceImpl(dataIn, 0);
}

void uniqueInPlaceImpl(T)(ref T[] dataIn, size_t start) {
    if(dataIn.length - start < 2)
        return;

    invariant T sentinel = dataIn[start];
    T[] data = dataIn[start + 1..$];

    static hash_t getHash(T elem) {
        static if(is(T == uint) || is(T == int)) {
            return cast(hash_t) elem;
        } else static if(__traits(compiles, elem.toHash)) {
            return elem.toHash;
        } else {
            static auto ti = typeid(typeof(elem));
            return ti.getHash(&elem);
        }
    }

    for(size_t index = 0; index < data.length;) {
        if(data[index] == sentinel) {
            index++;
            continue;
        }

        auto hash = getHash(data[index]) % data.length;
        if(index == hash) {
            index++;
            continue;
        }

        if(data[index] == data[hash]) {
            data[index] = sentinel;
            index++;
            continue;
        }

        if(data[hash] == sentinel) {
            swap(data[hash], data[index]);
            index++;
            continue;
        }

        auto hashHash = getHash(data[hash]) % data.length;
        if(hashHash != hash) {
            swap(data[index], data[hash]);
            if(hash < index)
                index++;
        } else {
            index++;
        }
    }


    size_t swapPos = 0;
    foreach(i; 0..data.length) {
        if(data[i] != sentinel && i == getHash(data[i]) % data.length) {
            swap(data[i], data[swapPos++]);
        }
    }

    size_t sentinelPos = data.length;
    for(size_t i = swapPos; i < sentinelPos;) {
        if(data[i] == sentinel) {
            swap(data[i], data[--sentinelPos]);
        } else {
            i++;
        }
    }

    dataIn = dataIn[0..sentinelPos + start + 1];
    uniqueInPlaceImpl(dataIn, start + swapPos + 1);
}

1
매우 멋지고 과소 평가 된 답변입니다! 나는 위치 1의 요소를 센티넬 값으로 사용하는 아이디어를 좋아합니다. 몇 가지 작은 제안을 할 수 있다면 "각 요소가 배열 크기 의 해시 모듈로에 해당하는 위치에 있습니다"를 포함하도록 2 단계를 변경 하고 센티넬에 설정할 중복 항목이 동일한 값을 갖는 요소 (동일한 해시 또는 동일한 해시 모듈로 배열 크기와 반대).
j_random_hacker

20

한 가지 더 효율적인 구현

int i, j;

/* new length of modified array */
int NewLength = 1;

for(i=1; i< Length; i++){

   for(j=0; j< NewLength ; j++)
   {

      if(array[i] == array[j])
      break;
   }

   /* if none of the values in index[0..j] of array is not same as array[i],
      then copy the current value to corresponding new position in array */

  if (j==NewLength )
      array[NewLength++] = array[i];
}

이 구현에서는 배열을 정렬 할 필요가 없습니다. 또한 중복 요소가 발견되면 그 이후의 모든 요소를 ​​한 위치만큼 이동할 필요가 없습니다.

이 코드의 출력은 NewLength 크기의 array []입니다.

여기서 우리는 배열의 두 번째 요소부터 시작하여이 배열까지 배열의 모든 요소와 비교합니다. 입력 배열을 수정하기 위해 추가 인덱스 변수 'NewLength'를 보유하고 있습니다. NewLength 변수는 0으로 초기화됩니다.

array [1]의 요소는 array [0]과 비교됩니다. 서로 다르면 array [NewLength]의 값이 array [1]로 수정되고 NewLength가 증가합니다. 동일하면 NewLength가 수정되지 않습니다.

따라서 배열 [1 2 1 3 1]이 있으면

'j'루프의 첫 번째 패스에서 array [1] (2)는 array0과 비교되고 2는 array [NewLength] = array [1]에 기록되므로 NewLength = 2이므로 배열은 [1 2]가됩니다.

'j'루프의 두 번째 패스에서 array [2] (1)는 array0 및 array1과 비교됩니다. 여기서 array [2] (1)과 array0은 동일한 루프이므로 여기서 중단됩니다. 따라서 배열은 NewLength = 2이므로 [1 2]가됩니다.

등등


3
멋지네요. 개선 할 제안이 있습니다. (; J <는 newLength, J = 0, J ++)을 제 중첩 루프를 위해 변경 될 수있는 검사가 변경 될 수 있는지와 마지막 경우 (j ==는 newLength)로
Vadakkumpadath

그것은 훌륭한 제안이었습니다. 나는 ur 코멘트에 따라 코드를 업데이트했습니다
Byju

어레이 {1,1,1,1,1,1}에 동일한 값이 있으면 실패합니다. 쓸모없는 코드.
YURIY Chernyshov

이것의 복잡성은 무엇입니까, 그것도 O (n ^ 2) 아닌가요?
JavaSa

1
너무 많은 upvotes, 그러나 이것은 효율적이지 않습니다. 중복이 거의 없을 때 O (n ^ 2)입니다.
폴 Hankin

19

우수한 O 표기법을 찾고 있다면 O (n log n) 정렬로 배열을 정렬 한 다음 O (n) 순회를 수행하는 것이 가장 좋은 방법 일 수 있습니다. 정렬하지 않고 O (n ^ 2)를보고 있습니다.

편집 : 정수만 수행하는 경우 기수 정렬을 수행하여 O (n)을 얻을 수도 있습니다.


Jeff B의 대답은 단지 O (n)입니다. 해시 세트와 해시 사전은 꿀벌 무릎입니다.
ChrisW

3
ChrisW : 충돌이 없다고 가정하면 해시 세트 / 사전은 O (1)입니다. (나는이 문제를 위해 그것들을 사용하지 않을 것이라고 말하는 것이 아닙니다. 아마 그럴 것입니다. 그들이 진정으로 O (1)라고 주장하는 것은 단지 오류 일뿐입니다.)
Laurence Gonsalves

2
실제로 배열의 크기를 미리 알고 있으므로 O (1)을 보장 할 수 있습니다. 그런 다음 충돌과 사용하는 추가 메모리 양을 절충 할 수 있습니다.
Vitali

문제에 대한 새로 게시 된 조건으로 인해 Jeff B의 솔루션이 무효화되는 그 반대 투표를 다시 생각할 수 있습니다.
Mark Ransom

3
순진한 삭제 방법은 많은 수의 중복에 대해 O (n ^ 2)를 초래할 수 있으므로 "순회"에 대해 자세히 설명 할 수 있습니다.
Mark Ransom

11

1. O (n log n) 시간에 O (1) 추가 공간 사용

예를 들면 다음과 같습니다.

  • 먼저 내부 O (n log n) 정렬을 수행합니다.
  • 그런 다음 목록을 한 번 살펴보고 목록의 시작 부분에 모든 백의 첫 번째 인스턴스를 작성하십시오.

ejel의 파트너가이 작업을 수행하는 가장 좋은 방법은 간단한 병합 단계를 사용하는 내부 병합 정렬이며, 예를 들어 질문의 의도 일 수 있다는 것이 옳다고 생각합니다. 입력을 개선 할 능력없이 가능한 한 효율적으로이를 수행하기 위해 새 라이브러리 함수를 작성하며, 입력의 종류에 따라 해시 테이블없이이를 수행하는 것이 유용한 경우가 있습니다. 그러나 나는 이것을 실제로 확인하지 않았습니다.

2. O (n) 시간에 O (lots) 추가 공간 사용

  • 모든 정수를 담을 수있을만큼 충분히 큰 0 배열을 선언
  • 어레이를 한 번 살펴보십시오.
  • 각 정수에 대해 해당 배열 요소를 1로 설정하십시오.
  • 이미 1이면 해당 정수를 건너 뜁니다.

이것은 몇 가지 의심스러운 가정이있는 경우에만 작동합니다.

  • 저렴하게 메모리를 제로화하는 것이 가능하거나 int의 크기가 개수에 비해 작습니다.
  • OS에 256 ^ sizepof (int) 메모리를 요청하시면됩니다.
  • 거대하다면 정말 효율적으로 캐시합니다.

잘못된 대답이지만 입력 요소가 많지만 모두 8 비트 정수 (또는 16 비트 정수일 수도 있음) 인 경우 가장 좋은 방법이 될 수 있습니다.

3. O (작은)-같은 여분의 공간, O (n)-쉬운 시간

# 2로 해시 테이블을 사용합니다.

4. 명확한 방법

요소 수가 적 으면 다른 코드가 더 빨리 작성되고 더 빨리 읽을 수 있으면 적절한 알고리즘을 작성하는 것이 유용하지 않습니다.

예 : 모든 동일한 요소를 제거하는 각 고유 요소 (즉, 첫 번째 요소, 두 번째 요소 (제거 된 첫 번째 요소의 중복) 등)에 대한 배열을 살펴 봅니다. O (1) 추가 공간, O (n ^ 2) 시간.

예 : 이를 수행하는 라이브러리 함수를 사용하십시오. 효율성은 쉽게 사용할 수있는 항목에 따라 다릅니다.


7

음, 기본 구현은 매우 간단합니다. 모든 요소를 ​​살펴보고 나머지 요소에 중복 항목이 있는지 확인하고 나머지 요소를 이동합니다.

끔찍한 비효율적이며 출력 또는 정렬 / 이진 트리에 대한 도우미 배열로 속도를 높일 수 있지만 허용되지 않는 것 같습니다.


1
OTOH, 정렬 트리를 구현하는 데 필요한 추가 코드는 간단한 솔루션보다 덜 (메모리) 효율적일 수 있으며, 작은 (예 : 100 개 미만의 요소) 배열의 경우 런타임에서 효율성이 떨어질 수 있습니다.
TMN

6

C ++를 사용할 수있는 경우를 호출 한 std::sort다음를 호출 std::unique하면 응답이 제공됩니다. 시간 복잡도는 정렬의 경우 O (N log N)이고 고유 순회의 경우 O (N)입니다.

그리고 C ++가 테이블에서 벗어난 경우 이러한 동일한 알고리즘이 C로 작성되지 못하도록하는 것은 없습니다.


"한 가지주의 할 점은 예상되는 알고리즘이 배열을 먼저 정렬 할 필요가 없다는 것입니다."
sbi

2
일단 배열을 정렬 할 수 없다고 말하지 않습니다. O (N) 외부 메모리를 사용하지 않고 정렬하는 것이 O (N log N) 이상에서 수행하는 유일한 방법입니다.
Greg Rogers

문제의 목적을 위해 표준 라이브러리 유틸리티를 사용해서는 안됩니다. 그러나 분류에 관해서는 생각할수록 괜찮은지 아닌지 확실하지 않습니다.
ejel

1
C ++ 및 C ++ 표준 함수를 참조하는 답변은 나중에이 질문을 찾는 사람들에게 더 둥근 답변을 제공하기 때문에 원래 질문에 답변하지 않더라도 유용하다고 생각합니다.
Douglas Leeder

6

메모리를 희생하려는 경우 단일 순회에서이를 수행 할 수 있습니다. 해시 / 연관 배열에서 정수를 보았는지 여부를 간단히 계산할 수 있습니다. 이미 숫자를 본 경우, 이동하면서 제거하거나, 보지 못한 숫자를 새 배열로 이동하여 원래 배열의 이동을 피하십시오.

Perl에서 :

foreach $i (@myary) {
    if(!defined $seen{$i}) {
        $seen{$i} = 1;
        push @newary, $i;
    }
}

답이 원래 배열에 있어야하는지 확실하지 않습니다.
Douglas Leeder

새 배열을 필요로하지 않고이를 수행하려면 단순히 중복 항목을 배열 끝에서 튀어 나온 요소로 교체하고 문제가 순서가 중요하다는 것을 지정하지 않으므로 현재 루프를 다시 실행할 수 있습니다. 이것은 약간의 추가 경계 검사가 필요하지만 매우 가능합니다.
Jeff B

6
질문이 수정 될 때까지 이것은 좋은 생각이었습니다. 해시 테이블 아이디어는 분명히 규칙에 위배됩니다.
WCWedin

14
이 답변이 가장 많이 득표되는 이유를 모르겠습니다. 그것은 perl로 작성되었고 C에서 사용할 수없는 중요한 기능을 사용합니다.
LiraNuna

5
질문은 perl이 아닌 c 코드를 요구했습니다. Perl을 사용하면 해시 테이블과 "푸시"를 무료로 얻을 수 있습니다. 내가 스칼라에서 그것을 할 수 있다면 당신은 input.removeDuplicates를 부를 것이다,하지만 난 그 :) 면접관을 허용했을 의심
피터 Recore

5

함수의 반환 값은 고유 한 요소의 수 여야하며 모두 배열의 맨 앞에 저장됩니다. 이 추가 정보가 없으면 중복 항목이 있는지조차 알 수 없습니다.

외부 루프의 각 반복은 배열의 한 요소를 처리합니다. 고유 한 경우 배열의 앞쪽에 있고 중복 된 경우 배열의 마지막 처리되지 않은 요소로 덮어 씁니다. 이 솔루션은 O (n ^ 2) 시간에 실행됩니다.

#include <stdio.h>
#include <stdlib.h>

size_t rmdup(int *arr, size_t len)
{
  size_t prev = 0;
  size_t curr = 1;
  size_t last = len - 1;
  while (curr <= last) {
    for (prev = 0; prev < curr && arr[curr] != arr[prev]; ++prev);
    if (prev == curr) {
      ++curr;
    } else {
      arr[curr] = arr[last];
      --last;
    }
  }
  return curr;
}

void print_array(int *arr, size_t len)
{
  printf("{");
  size_t curr = 0;
  for (curr = 0; curr < len; ++curr) {
    if (curr > 0) printf(", ");
    printf("%d", arr[curr]);
  }
  printf("}");
}

int main()
{
  int arr[] = {4, 8, 4, 1, 1, 2, 9};
  printf("Before: ");
  size_t len = sizeof (arr) / sizeof (arr[0]);
  print_array(arr, len);
  len = rmdup(arr, len);
  printf("\nAfter: ");
  print_array(arr, len);
  printf("\n");
  return 0;
}

4

다음은 Java 버전입니다.

int[] removeDuplicate(int[] input){

        int arrayLen = input.length;
        for(int i=0;i<arrayLen;i++){
            for(int j = i+1; j< arrayLen ; j++){
                if(((input[i]^input[j]) == 0)){
                    input[j] = 0;
                }
                if((input[j]==0) && j<arrayLen-1){
                        input[j] = input[j+1];
                        input[j+1] = 0;
                    }               
            }
        }       
        return input;       
    }

최소한 다음 입력에서 실패 : {1,1,1,1,1,1,1} {0,0,0,0,0,1,1,1,1,1,1}
Yuriy Chernyshov

3

여기 내 해결책이 있습니다.

///// find duplicates in an array and remove them

void unique(int* input, int n)
{
     merge_sort(input, 0, n) ;

     int prev = 0  ;

     for(int i = 1 ; i < n ; i++)
     {
          if(input[i] != input[prev])
               if(prev < i-1)
                   input[prev++] = input[i] ;                         
     }
}

2

값을 앞뒤로 불필요하게 복사하지 않도록 배열은 분명히 오른쪽에서 왼쪽으로 "순회"되어야합니다.

무제한 메모리 sizeof(type-of-element-in-array) / 8가있는 경우 각 비트가 이미 해당 값을 만났는지 여부를 나타내도록 바이트에 비트 배열을 할당 할 수 있습니다 .

그렇지 않으면 배열을 순회하고 각 값을 그 뒤에 오는 값과 비교 한 다음 중복이 발견되면이 값을 모두 제거하는 것보다 더 나은 것을 생각할 수 없습니다. 이것은 O (n ^ 2) (또는 O ((n ^ 2-n) / 2) ) 근처에 있습니다.

IBM 에 좀 가까운 주제에 대한 기사 가 있습니다.


실제로 가장 큰 요소를 찾기위한 O (n) 패스는 전체 O () 비용을 증가시키지 않습니다.
Douglas Leeder

2

보자 :

  • 최소 / 최대 할당을 찾기위한 O (N) 통과
  • 찾은 비트 어레이
  • O (N) 패스 스와핑 중복을 끝냅니다.

그것들이 단지 정수라는 점을 감안할 때, 단순성을 위해 32 비트를 가정하고 최소 / 최대를 찾지 않아도됩니다. 2 ^ 32 비트는 "오직"512MB이므로 경계를 찾는 것은 메모리 사용과 O (1) 시간 최적화 일뿐입니다. (주어진 예제의 경우 상당한 최적화가 허용됨). 64 비트라면 최소값과 최대 값이 메모리의 비트 수보다 더 멀리 떨어져 있지 않다는 사실을 모르기 때문에 관련이 없습니다.
Steve Jessop

이론을 제쳐두고 512MB를 할당하는 것이 최소 / 최대를 찾는 것보다 더 많은 시간이 걸리지 않을까요?
LiraNuna

데이터의 양과 최소 / 최대가 무엇인지에 따라 다릅니다. 512MB 이상의 입력을보고 있다면 추가 O (N) 패스를 피하는 것이 더 빠를 수 있습니다. 물론 그렇게 많은 입력을보고 있다면 여유 공간이 512MB 일 가능성이 적습니다. 최소 / 최대가 0 / INT_MAX에 가까운 경우 최적화도 도움이되지 않습니다. 첫 번째 단계는 분명히 작은 숫자에 도움이되지만이 알고리즘이 최악의 경우 UINT_MAX 비트를 사용한다는 사실을 피할 수는 없으므로 그 제한에 대한 계획을 세워야합니다.
Steve Jessop

당신이 옳을 수 있습니다-어떤 경우 에든 질문의 설명은 비트 어레이를 사용하는 것을 의미합니다. 누군가가 제약없이 나중에 와서 가능한 모든 답변을보고 싶어하는 경우에 대비해이 답변을 남겨 두겠습니다.
Douglas Leeder

2

이것은 O (N log N) 알고리즘을 사용하고 추가 스토리지없이 한 번에 수행 할 수 있습니다.

요소 a[1]에서 a[N]. 각 단계 i에서의 왼쪽에있는 모든 요소 a[i]는 정렬 된 요소 힙을 구성 a[0]합니다 a[j]. 한편, 두 번째 인덱스 j(처음에는 0)는 힙 크기를 추적합니다.

검사 a[i]및 지금 요소를 차지 힙, 삽입 a[0]a[j+1]. 요소가 삽입 될 a[k]때 동일한 값을 갖는 중복 요소 가 발견 a[i]되면 힙에 삽입하지 마십시오 (즉, 폐기하십시오). 그렇지 않으면 힙에 삽입하십시오. 이제 한 요소만큼 증가하고 이제 a[0]to a[j+1]및 increment로 구성 j됩니다.

이 방식으로 계속해서 i모든 배열 요소가 검사되고 힙에 삽입 될 때까지 증가 a[0]하여 a[j]. j힙의 마지막 요소의 인덱스이고 힙에는 고유 한 요소 값만 포함됩니다.

int algorithm(int[] a, int n)
{
    int   i, j;  

    for (j = 0, i = 1;  i < n;  i++)
    {
        // Insert a[i] into the heap a[0...j]
        if (heapInsert(a, j, a[i]))
            j++;
    }
    return j;
}  

bool heapInsert(a[], int n, int val)
{
    // Insert val into heap a[0...n]
    ...code omitted for brevity...
    if (duplicate element a[k] == val)
        return false;
    a[k] = val;
    return true;
}

예제를 보면 결과 배열이 원래 요소 순서를 유지하기 때문에 정확히 요청 된 것이 아닙니다. 그러나이 요구 사항이 완화되면 위의 알고리즘이 트릭을 수행해야합니다.


1

Java에서는 이렇게 해결할 것입니다. 이것을 C로 작성하는 방법을 모릅니다.

   int length = array.length;
   for (int i = 0; i < length; i++) 
   {
      for (int j = i + 1; j < length; j++) 
      {
         if (array[i] == array[j]) 
         {
            int k, j;
            for (k = j + 1, l = j; k < length; k++, l++) 
            {
               if (array[k] != array[i]) 
               {
                  array[l] = array[k];
               }
               else
               {
                  l--;
               }
            }
            length = l;
         }
      }
   }

배열의 끝에있는 값으로 찾은 중복을 덮어 쓰면 내부 for () 루프에서 전체 배열의 이동을 피할 수 있습니다. 그러면 O (n ^ 3)에서 O (n ^ 2)로 이동합니다. 내 C 구현이 여기 어딘가에 떠 있습니다 ...
mocj 2010 년

변속이 요구 사항의 일부라고 생각했지만 당연히 맞습니다.
Dominik

1
@mocj : 당신의 솔루션이 마음에 들어요. 그러나 마지막 두 요소가 같으면 마지막 두 요소가 같으면 작동하지 않는 것 같습니다. (다른 곳에서 댓글을 달기에는 평판이 너무 높기 때문에 여기에 등장 :()
Dominik

원래 문제가 배열 끝의 값이 무시할 만하다는 것을 제외하고는 맞습니다. 수정 된 배열의 길이를 반환하지 않기 때문에 두 값이 같을 때 마지막 값과 마지막 값의 차이는 중요하지 않습니다. 호출자는 반환 된 배열의 끝을 어디로 해석
합니까

1

다음은 어떻습니까?

int* temp = malloc(sizeof(int)*len);
int count = 0;
int x =0;
int y =0;
for(x=0;x<len;x++)
{
    for(y=0;y<count;y++)
    {
        if(*(temp+y)==*(array+x))
        {
            break;
        }
    }
    if(y==count)
    {
        *(temp+count) = *(array+x);
        count++;
    }
}
memcpy(array, temp, sizeof(int)*len);

임시 배열을 선언하고 모든 요소를 ​​원래 배열에 다시 복사하기 전에 여기에 요소를 넣으려고합니다.


1

문제를 검토 한 후 여기 내 델파이 방식이 있습니다.

var
A: Array of Integer;
I,J,C,K, P: Integer;
begin
C:=10;
SetLength(A,10);
A[0]:=1; A[1]:=4; A[2]:=2; A[3]:=6; A[4]:=3; A[5]:=4;
A[6]:=3; A[7]:=4; A[8]:=2; A[9]:=5;

for I := 0 to C-1 do
begin
  for J := I+1 to C-1 do
    if A[I]=A[J] then
    begin
      for K := C-1 Downto J do
        if A[J]<>A[k] then
        begin
          P:=A[K];
          A[K]:=0;
          A[J]:=P;
          C:=K;
          break;
        end
        else
        begin
          A[K]:=0;
          C:=K;
        end;
    end;
end;

//tructate array
setlength(A,C);
end;

1

다음 예는 문제를 해결합니다.

def check_dump(x):
   if not x in t:
      t.append(x)
      return True

t=[]

output = filter(check_dump, input)

print(output)
True

1
import java.util.ArrayList;


public class C {

    public static void main(String[] args) {

        int arr[] = {2,5,5,5,9,11,11,23,34,34,34,45,45};

        ArrayList<Integer> arr1 = new ArrayList<Integer>();

        for(int i=0;i<arr.length-1;i++){

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

        for(int i=0;i<arr.length;i++){
            if(arr[i] != 99999){

                arr1.add(arr[i]);
            }
        }

        System.out.println(arr1);
}
    }

arr [i + 1]은 마지막 요소에 대해 ArrayIndexOutOfBoundsException을 발생시켜야합니까?
Sathesh

@Sathesh 번호 "<arr.length-1"때문에
GabrielBB

1

이것은 순진한 (N * (N-1) / 2) 솔루션입니다. 일정한 추가 공간을 사용하고 원래 순서를 유지합니다. @Byju의 솔루션과 유사하지만 if(){}블록을 사용하지 않습니다 . 또한 요소를 자신에게 복사하는 것을 방지합니다.

#include <stdio.h>
#include <stdlib.h>

int numbers[] = {4, 8, 4, 1, 1, 2, 9};
#define COUNT (sizeof numbers / sizeof numbers[0])

size_t undup_it(int array[], size_t len)
{
size_t src,dst;

  /* an array of size=1 cannot contain duplicate values */
if (len <2) return len; 
  /* an array of size>1 will cannot at least one unique value */
for (src=dst=1; src < len; src++) {
        size_t cur;
        for (cur=0; cur < dst; cur++ ) {
                if (array[cur] == array[src]) break;
                }
        if (cur != dst) continue; /* found a duplicate */

                /* array[src] must be new: add it to the list of non-duplicates */
        if (dst < src) array[dst] = array[src]; /* avoid copy-to-self */
        dst++;
        }
return dst; /* number of valid alements in new array */
}

void print_it(int array[], size_t len)
{
size_t idx;

for (idx=0; idx < len; idx++)  {
        printf("%c %d", (idx) ? ',' :'{' , array[idx] );
        }
printf("}\n" );
}

int main(void) {    
    size_t cnt = COUNT;

    printf("Before undup:" );    
    print_it(numbers, cnt);    

    cnt = undup_it(numbers,cnt);

    printf("After undup:" );    
    print_it(numbers, cnt);

    return 0;
}

0

이것은 단일 패스, 입력 목록의 정수 수 O (N) 시간, 고유 정수 수 O (N) 저장으로 수행 할 수 있습니다.

두 개의 포인터 "dst"및 "src"가 첫 번째 항목으로 초기화 된 상태에서 목록을 앞뒤로 살펴 봅니다. "본인 정수"의 빈 해시 테이블로 시작합니다. src의 정수가 해시에 없으면 dst의 슬롯에 쓰고 dst를 증가시킵니다. src의 정수를 해시에 추가 한 다음 src를 증가시킵니다. src가 입력 목록의 끝을 통과 할 때까지 반복합니다.


2
원래 질문에 대한 수정에서 해시 테이블은 허용되지 않습니다. 두 포인터 접근 방식은 중복을 식별 한 후에 출력을 압축하는 좋은 방법입니다.
Mark Ransom

0

binary tree the disregards duplicates- 에 모든 요소를 ​​삽입합니다 O(nlog(n)). 그런 다음 순회를 수행하여 배열에서 모두 다시 추출합니다 O(n). 주문 보존이 필요하지 않다고 가정합니다.


0

해싱을 위해 블룸 필터를 사용합니다. 이렇게하면 메모리 오버 헤드가 크게 줄어 듭니다.


자세히 설명하거나 참조를 제공 하시겠습니까?
dldnh

0

JAVA에서는

    Integer[] arrayInteger = {1,2,3,4,3,2,4,6,7,8,9,9,10};

    String value ="";

    for(Integer i:arrayInteger)
    {
        if(!value.contains(Integer.toString(i))){
            value +=Integer.toString(i)+",";
        }

    }

    String[] arraySplitToString = value.split(",");
    Integer[] arrayIntResult = new Integer[arraySplitToString.length];
    for(int i = 0 ; i < arraySplitToString.length ; i++){
        arrayIntResult[i] = Integer.parseInt(arraySplitToString[i]);
    }

출력 : {1, 2, 3, 4, 6, 7, 8, 9, 10}

이것이 도움이되기를 바랍니다


1
입력으로 테스트arrayInteger = {100,10,1};
Blastfurnace 2012-06-23


0

먼저, check[n]n이 중복되지 않게하려는 배열의 요소 수인 배열을 생성하고 모든 요소 (검사 배열의) 값을 1로 설정해야합니다. for 루프를 사용하여 중복, 이름이라고 말하고 arrfor 루프에서 다음을 작성하십시오.

{
    if (check[arr[i]] != 1) {
        arr[i] = 0;
    }
    else {
        check[arr[i]] = 0;
    }
}

이를 통해 모든 중복을 0으로 설정합니다. 따라서 남은 일은 arr배열 을 가로 질러 0이 아닌 모든 것을 인쇄하는 것입니다. 주문은 유지되고 선형 시간 (3 * n)이 걸립니다.


이 질문은 추가 데이터 구조를 사용하는 것을 허용하지 않습니다.
ejel

0

n 개의 요소의 배열이 주어지면 O (nlogn) 시간에 배열에서 모든 중복을 제거하는 알고리즘을 작성하십시오.

Algorithm delete_duplicates (a[1....n])
//Remove duplicates from the given array 
//input parameters :a[1:n], an array of n elements.

{

temp[1:n]; //an array of n elements. 

temp[i]=a[i];for i=1 to n

 temp[i].value=a[i]

temp[i].key=i

 //based on 'value' sort the array temp.

//based on 'value' delete duplicate elements from temp.

//based on 'key' sort the array temp.//construct an array p using temp.

 p[i]=temp[i]value

  return p.

다른 요소는 '키'를 사용하여 출력 배열에서 유지됩니다. 키의 길이가 O (n)이고 키와 값에 대해 정렬을 수행하는 데 걸리는 시간이 O (nlogn)라고 가정합니다. 따라서 어레이에서 모든 중복 항목을 삭제하는 데 걸리는 시간은 O (nlogn)입니다.


모든 굵은 글리프에 대해 무엇을 만들었 helper data structure (e.g. hashtable) should not be used습니까?
greybeard 2015 년

반드시 필요한 것은 아닙니다. 나는 이해를 위해 그것들을 강조했습니다.
Sharief Muzammil 2015 년

0

이것은 내가 가진 것입니다. 비록 그것을 고치기 위해 우리가 오름차순 또는 내림차순으로 정렬 할 수있는 순서를 잘못 배치했습니다.

#include <stdio.h>
int main(void){
int x,n,myvar=0;
printf("Enter a number: \t");
scanf("%d",&n);
int arr[n],changedarr[n];

for(x=0;x<n;x++){
    printf("Enter a number for array[%d]: ",x);
    scanf("%d",&arr[x]);
}
printf("\nOriginal Number in an array\n");
for(x=0;x<n;x++){
    printf("%d\t",arr[x]);
}

int i=0,j=0;
// printf("i\tj\tarr\tchanged\n");

for (int i = 0; i < n; i++)
{
    // printf("%d\t%d\t%d\t%d\n",i,j,arr[i],changedarr[i] );
    for (int j = 0; j <n; j++)
    {   
        if (i==j)
        {
            continue;

        }
        else if(arr[i]==arr[j]){
            changedarr[j]=0;

        }
        else{
            changedarr[i]=arr[i];

        }
    // printf("%d\t%d\t%d\t%d\n",i,j,arr[i],changedarr[i] );
    }
    myvar+=1;
}
// printf("\n\nmyvar=%d\n",myvar);
int count=0;
printf("\nThe unique items:\n");
for (int i = 0; i < myvar; i++)
{
        if(changedarr[i]!=0){
            count+=1;
            printf("%d\t",changedarr[i]);   
        }
}
    printf("\n");
}

-1

정수가 포함되어 있는지 빠르게 알 수있는 좋은 DataStructure가 있다면 멋질 것입니다. 아마도 어떤 종류의 나무 일 것입니다.

DataStructure elementsSeen = new DataStructure();
int elementsRemoved = 0;
for(int i=0;i<array.Length;i++){
  if(elementsSeen.Contains(array[i])
    elementsRemoved++;
  else
    array[i-elementsRemoved] = array[i];
}
array.Length = array.Length - elementsRemoved;
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.