web-dev-qa-db-ja.com

アルゴリズム:配列から重複した整数を削除する効率的な方法

この問題は、マイクロソフトとのインタビューから得ました。

ランダムな整数の配列が与えられた場合、重複した数値を削除し、元の配列の一意の数値を返すアルゴリズムをCで記述します。

例:入力:{4, 8, 4, 1, 1, 2, 9}出力:{4, 8, 1, 2, 9, ?, ?}

1つの注意点は、予想されるアルゴリズムでは、配列を最初にソートする必要がないことです。また、要素が削除されたら、次の要素も前方に移動する必要があります。とにかく、要素が前方にシフトされた配列の末尾の要素の値は無視できます。

更新:結果は元の配列で返される必要があり、ヘルパーデータ構造(ハッシュテーブルなど)は使用しないでください。ただし、順序の保存は必要ないと思います。

pdate2:これらの非現実的な制約を疑問に思う人のために、これはインタビューの質問であり、これらの制約はすべて、思考プロセスの中で議論され、さまざまなアイデアを思いつくことができます。

86
ejel

どうですか:

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)以下にする必要があります。

19
mocj

彼女が提案する解決策は、マージソートのバリエーションです。唯一の変更は、マージ手順中に、重複した値を無視することです。このソリューションも同様にO(n log n)になります。このアプローチでは、並べ替え/重複の除去が組み合わされます。ただし、それが違いを生むかどうかはわかりません。

135
ejel

これは以前SOに投稿したことがありますが、かなりクールなので、ここで再現します。ハッシュを使用して、所定の場所にハッシュセットのようなものを構築します。 x窩空間ではO(1)であることが保証されており(再帰は末尾呼び出しです)、通常はO(N)時間の複雑さです。アルゴリズムは次のとおりです。

  1. 配列の最初の要素を取ります。これはセンチネルになります。
  2. 各要素がそのハッシュに対応する位置にあるように、配列の残りを可能な限り並べ替えます。この手順が完了すると、重複が検出されます。それらをセンチネルに等しく設定します。
  3. インデックスがハッシュと等しいすべての要素を配列の先頭に移動します。
  4. 配列の最初の要素を除き、センチネルに等しいすべての要素を配列の最後に移動します。
  5. 適切にハッシュされた要素と重複した要素の間に残っているのは、衝突のためにハッシュに対応するインデックスに配置できなかった要素です。これらの要素を処理するために再帰します。

これはO(N)であることが示されます。ハッシングに病理学的シナリオがない場合:重複がなくても、各再帰で約2/3の要素が削除されます。再帰の各レベルはO(n)です。ここで、小さいnは残っている要素の量です。唯一の問題は、実際には、重複がほとんどない、つまり衝突が多い場合、クイックソートよりも遅いということです。ただし、大量の重複がある場合、驚くほど高速です。

編集:Dの現在の実装では、hash_tは32ビットです。このアルゴリズムに関するすべてのことは、完全な32ビット空間でのハッシュ衝突が非常に少ないと仮定しています。ただし、モジュラス空間では衝突が頻繁に発生する場合があります。ただし、この仮定は、合理的にサイズ設定されたデータセットのすべてに当てはまる可能性があります。キーが32ビット以下の場合、それは独自のハッシュになる可能性があります。つまり、32ビットの完全なスペースでの衝突は不可能です。大きい場合は、32ビットのメモリアドレススペースに十分に収まらないため、問題になります。 hash_tは、データセットが大きくなる可能性があるDの64ビット実装で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);
}
46
dsimcha

もう1つの効率的な実装

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];
}

この実装では、配列をソートする必要はありません。また、重複する要素が見つかった場合、この後のすべての要素を1ポジションだけシフトする必要はありません。

このコードの出力は、サイズがNewLengthのarray []です。

ここでは、配列の2番目の要素から開始し、この配列までの配列内のすべての要素と比較します。入力配列を変更するための追加のインデックス変数「NewLength」を保持しています。 NewLength variabelは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なのでarrayは[1 2]になります。

'j'ループの2番目のパスでは、array [2](1)がarray0およびarray1と比較されます。ここでは、array [2](1)とarray0が同じループなので、ここで中断します。 NewLength = 2であるため、配列は[1 2]になります

等々

20
Byju

優れたO表記を探している場合は、O(n log n)ソートで配列をソートしてからO(n)トラバーサルを実行するのが最適な方法です。ソートせずに、O(n ^ 2)を見ています。

編集:整数を実行している場合、基数ソートを実行してO(n)を取得することもできます。

19
carl

1。O(1)余分なスペースを使用して、O(n log n)時間で

たとえば、これは可能です。

  • 最初にインプレースO(n log n)ソートを実行します
  • 次に、リストを1回歩いて、すべての最初のインスタンスをリストの先頭に書き込みます

Ejelのパートナーは、これを行う最良の方法は、単純化されたマージステップを使用したインプレースマージソートであり、おそらくそれが質問の意図であると正しいと思います。これを可能な限り効率的に行うための新しいライブラリ関数を作成し、入力を改善することはできません。また、入力の種類によっては、ハッシュテーブルなしで行うことが有用な場合があります。しかし、私は実際にこれをチェックしていません。

2。O(lots)余分なスペースを使用して、O(n) time

  • すべての整数を保持するのに十分な大きさのゼロの配列を宣言します
  • 一度配列を歩く
  • 整数ごとに対応する配列要素を1に設定します。
  • 既に1だった場合、その整数をスキップします。

これは、いくつかの疑わしい仮定が当てはまる場合にのみ機能します。

  • メモリを安価にゼロにするか、intのサイズがそれらの数と比較して小さい
  • oSに256 ^ sizepof(int)メモリを要求して満足です
  • 巨大な場合、本当に効率的にキャッシュされます

それは悪い答えですが、入力要素がたくさんあるが、それらがすべて8ビット整数(または16ビット整数)である場合、それが最善の方法です。

。O(少し)-ish余分なスペース、O(n)-ish time

#2と同じですが、ハッシュテーブルを使用します。

4。明確な方法

要素の数が少ない場合、適切なアルゴリズムを書くことは、他のコードの記述が速く、読みやすい場合は役に立ちません。

例えば。一意の各要素(つまり、最初の要素、2番目の要素(最初の要素の重複が削除された)など)の配列をすべて調べて、同一の要素をすべて削除します。 O(1)余分なスペース、O(n ^ 2)時間。

例えば。これを行うライブラリ関数を使用します。効率は簡単に利用できるものに依存します。

10
Jack V.

基本的な実装は非常に簡単です。すべての要素を調べて、残りの要素に重複があるかどうかを確認し、残りをそれらの上に移動します。

それはひどく非効率的であり、出力用のヘルパー配列またはソート/バイナリツリーによって速度を上げることができますが、これは許可されていないようです。

7
Dario

メモリを犠牲にする場合は、1回の走査でこれを実行できます。ハッシュ/連想配列で整数を見たかどうかだけで集計できます。既に数字を見ている場合は、移動しながらそれを削除するか、見たくない数字を新しい配列に移動して、元の配列に移動しないようにします。

Perlの場合:

foreach $i (@myary) {
    if(!defined $seen{$i}) {
        $seen{$i} = 1;
        Push @newary, $i;
    }
}
6
Jeff B

C++の使用が許可されている場合、std::sortの呼び出しに続いてstd::uniqueの呼び出しを行うと、答えが得られます。時間の複雑さは、ソートの場合はO(N log N)、一意のトラバーサルの場合はO(N)です。

また、C++がテーブルから外れている場合、これらの同じアルゴリズムがCで記述されないようにするものはありません。

6
fbrereto

関数の戻り値は一意の要素の数である必要があり、それらはすべて配列の先頭に格納されます。この追加情報がないと、重複があるかどうかさえわかりません。

外側のループの各反復は、配列の1つの要素を処理します。一意である場合、配列の先頭に残り、重複している場合、配列内の最後の未処理の要素によって上書きされます。このソリューションは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;
}
5
dsh

これが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;       
    }
4
Naren

配列は、値の前後への不必要なコピーを避けるために、明らかに右から左へ「トラバース」する必要があります。

メモリが無制限の場合、sizeof(type-of-element-in-array) / 8バイトのビット配列を割り当てて、対応する値に既に遭遇したかどうかを各ビットに示すことができます。

そうでない場合、配列を走査して各値をそれに続く値と比較し、重複が見つかった場合はこれらの値を完全に削除するよりも良い方法は考えられません。これはO(n ^ 2)(またはO((n ^ 2-n)/ 2 ))。

IBMには article があります。

2
Anton Gogolev

どれどれ:

  • 最小/最大割り当てを見つけるためのO(N)パス
  • 見つかったビット配列
  • O(N)は、重複するスワッピングを最後まで渡します。
2
Douglas Leeder

これが私の解決策です。

///// 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
kiriloff
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);
}
    }
1
Ankit Jain

これは単純な(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;
}
1
wildplasser

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;
         }
      }
   }
1
Dominik

問題を確認した後、ここに私のデルファイの方法があります。

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
RichardLi

これは、O(N log N)アルゴリズムを使用して1回のパスで実行でき、追加のストレージはありません。

要素a[1]からa[N]に進みます。各段階iで、a[i]の左側にあるすべての要素は、要素a[0]からa[j]までのソートされたヒープを構成します。一方、2番目のインデックスj(最初は0)は、ヒープのサイズを追跡します。

a[i]を調べて、ヒープに挿入します。ヒープは、エレメントa[0]からa[j+1]を占有します。要素が挿入されるときに、同じ値を持つ重複要素a[k]が検出された場合、a[i]をヒープに挿入しないでください(つまり、破棄します)。それ以外の場合は、ヒープに挿入します。ヒープは1つの要素で拡張され、a[0]からa[j+1]になり、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
David R Tribble

次はどうですか?

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
Charith

次の例で問題を解決できます。

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

t=[]

output = filter(check_dump, input)

print(output)
True
1
yupbank

これは、単一のパスで、入力リストの整数の数でO(N)時間、一意の整数の数でO(N)ストレージで実行できます。

2つのポインター「dst」と「src」を最初の項目に初期化して、リストを前から後ろに歩きます。 「整数を見た」の空のハッシュテーブルから始めます。 srcの整数がハッシュに存在しない場合、dstのスロットに書き込み、dstをインクリメントします。 srcの整数をハッシュに追加し、srcをインクリメントします。 srcが入力リストの最後を通過するまで繰り返します。

0
Andy Ross

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}

これが役立つことを願っています

0
PRABHU SEKAR

ハッシュにブルームフィルターを使用します。これにより、メモリオーバーヘッドが大幅に削減されます。

0
gaurav gupta

O(n)複雑さを持つ BinarySearchTree を作成します。

0
cpp

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.

他の要素では、 'key'を使用して出力配列に保持されます。キーの長さがO(n)であり、キーと値のソートにかかる時間はO(nlogn)であると考えてください。配列からすべての重複を削除するのにかかる時間はO(nlogn)です。

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");
}
0
ashim888

binary tree the disregards duplicates-O(nlog(n))にすべての要素を挿入します。次に、トラバーサルを実行して配列内のすべての要素を抽出します-O(n)。注文を保存する必要はないと思います。

0
Ashwin

最初に、配列check[n]を作成する必要があります。ここで、nは重複のない配列の要素数であり、すべての要素(チェック配列の)の値を1に設定します。重複して配列を走査し、その名前がarrであり、for-loopに次のように記述します。

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

それにより、すべての重複をゼロに設定します。したがって、行うべきことはarr配列を走査し、ゼロ以外のすべてを出力することだけです。順序は維持され、線形時間(3 * n)がかかります。

0
Grabenfly