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


92

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

ランダムな整数の配列を指定して、重複した数値を削除し、元の配列の一意の数値を返すアルゴリズムをCで記述します。

たとえば、入力:{4, 8, 4, 1, 1, 2, 9} 出力:{4, 8, 1, 2, 9, ?, ?}

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

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

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


4
一意の番号の順序を維持する必要がありますか?
ダグラスリーダー

1
結果は元の配列で返される必要がありますか?
Douglas Leeder、2009年

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
これは単純な解決策であり、インタビューの質問が求めているものよりもはるかに優れています。
カークブロードハースト

7
彼らもあなたにランタイム制約を与えていない限り、あなたが時期尚早な最適化にふけることに苦しんでいないことを確認しているかもしれません!:-)
Trevor Tippins

16
笑、配列を並べ替えて並べ替えた配列で作業するほうが断然高速です。並べ替えはAPIによって提供される必要があり、時期尚早の最適化ではありません。
ziggystar 2009年

2
while(current <= end)ではなく、while(current <= end)であるべきではありませんか?
Shail、2013

2
なぜこれが正しい答えとして受け入れられたのですか?順序を維持する必要がない場合は、マージソートO(nlogn)を使用して、O(n)...の複雑な要素をすべて削除する方が良いでしょう。これは、このソリューションよりもはるかに優れたO(nlogn)です。
パワン14年

136

私のガールフレンドが提案した解決策は、マージソートのバリエーションです。唯一の変更は、マージステップ中は重複する値を無視することだけです。このソリューションもO(n log n)になります。このアプローチでは、並べ替え/重複の削除が組み合わされます。ただし、それが何らかの違いをもたらすかどうかはわかりません。


8
すばらしい提案ですが、各マージ出力の終わりを追跡するために、いくらかの簿記が必要になります。私は実際にこれを1回行いました。マージするときに重複を排除することで、はるかに高速になります。
マークランサム

2
O(N / 2)の余分なスペースが質問で禁止されている「ヘルパーデータ構造」としてカウントされるかどうかは明確ではありません-制限がO(1)の余分なスペースを規定することを意図しているか、または単に答えは、ビッグオールのデータ構造の実装に依存すべきではありません。たぶん、標準のマージで問題ありません。しかし、そうでない場合は、ヒント:何をしているのか本当にわかっているのでない限り、面接でインプレースマージソートを記述しないでください。
Steve Jessop、

いい案。ただし、残りのデータは元の順序を維持する必要があります。
Hardy Feng

4
次のあなたのガールフレンドが提案内容を説明紙:dc-pubs.dbs.uni-leipzig.de/files/...
マイク・B

50

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

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

これは、ハッシュで病理学的シナリオがなければ、O(N)であると示されます。重複がない場合でも、再帰ごとに約2/3の要素が削除されます。再帰の各レベルはO(n)で、小さな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

もう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バリアベルは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」ループの2番目のパスでは、array [2](1)がarray0およびarray1と比較されます。ここで、array [2](1)とarray0は同じループなので、ここで壊れます。そのため、配列はNewLength = 2なので[1 2]になります。

等々


3
良いですね。改善する提案があります。2番目のネストされたループはfor(j = 0; j <NewLength; j ++)に変更でき、最後のチェックはif(j == NewLength)に変更できます
Vadakkumpadath

それは素晴らしい提案でした。私は
あなたの

少なくとも配列{​​1,1,1,1,1,1}に同じ値がある場合は失敗します。役に立たないコード。
Yuriy Chernyshov 2013年

さて、これの複雑さは何ですか、それはO(n ^ 2)でもありませんか?
JavaSa 2014年

1
非常に多くの賛成投票がありますが、これは効率的ではありません。重複が少ない場合はO(n ^ 2)です。
Paul Hankin、2015年

19

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

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


ジェフBの答えは単にO(n)です。ハッシュセットとハッシュ辞書はミツバチの膝です。
ChrisW、2009年

3
ChrisW:衝突がないと仮定した場合、ハッシュセット/辞書はO(1)のみになります。(私がこの問題にそれらを使用しないと言っているわけではありません-おそらく私はそうするでしょう-それらが本当にO(1)であると主張するのは単なる誤りです。)
Laurence Gonsalves

2
実際、事前に配列のサイズを知っているので、O(1)を保証できます。次に、衝突と、使用する追加メモリの量をトレードオフできます。
Vitali

あなたはその反対投票を再考したいと思うかもしれません-問題に新しく投稿された条件はジェフBの解を無効にします。
マークランサム

3
単純な消去方法では、多数の重複に対してO(n ^ 2)が発生する可能性があるため、「トラバーサル」について詳しく説明する必要がある場合があります。
マークランサム

11

1. O(n log n)時間でのO(1)余分なスペースの使用

これは可能です、例えば:

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

私はejelのパートナーが正しいと思います。これを行う最善の方法は、マージステップが簡略化されたインプレースマージソートであり、それがおそらく、たとえば、入力を改善する機能がない状態で、これを可能な限り効率的に行う新しいライブラリー関数を作成します。入力の種類によっては、ハッシュテーブルなしでそうすることが役立つ場合があります。しかし、私は実際にこれをチェックしていません。

2. O(n)時間でのO(lots)余分なスペースの使用

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

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

  • 低コストでメモリをゼロにすることが可能であるか、intのサイズがそれらの数に比べて小さい
  • OSに256 ^ sizepof(int)メモリを要求してもかまいません
  • それが巨大な場合、本当に効率的にキャッシュします

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

3. O(少し)っぽい余分なスペース、O(n)っぽい時間

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

4.明確な方法

要素の数が少ない場合、他のコードの書き込みと読み取りが速いと、適切なアルゴリズムを作成しても役に立ちません。

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

例えば。これを行うライブラリ関数を使用します。効率は、簡単に入手できるものによって異なります。


7

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

それはひどい非効率的であり、出力またはソート/バイナリツリーのヘルパー配列によってスピードアップできますが、これは許可されていないようです。


1
OTOH、並べ替えツリーを実装するために必要な追加コードは、単純なソリューションよりも(メモリ)効率が悪くなる可能性があり、小さな(たとえば、100要素より少ない)配列の実行時の効率はおそらく低くなります。
TMN

6

C ++の使用が許可されている場合、への呼び出しのstd::sort後にへの呼び出しを実行std::uniqueすると、答えが得られます。時間の複雑さは、並べ替えの場合はO(N log N)、一意の走査の場合はO(N)です。

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


「1つの注意点は、予期されるアルゴリズムでは配列を最初にソートする必要がないことです。」
sbi 2009年

2
一度取得したら配列をソートできないとは言わない... O(N)を使用せずに外部メモリのソートがO(N log N)以上でそれを行う唯一の方法です。
グレッグロジャース

問題の目的のために、標準ライブラリutilsを使用しないでください。でも、ソートに関しては、考えれば考えるほど、大丈夫かどうか迷っています。
ejel

1
C ++およびC ++の標準関数を参照する回答は、元の質問に回答しなくても、後でこの質問を見つけた人々により丸みを帯びた回答を提供するため、役立つと思います。
ダグラスリーダー

6

メモリを犠牲にしてもかまわない場合は、1回のトラバーサルでこれを行うことができます。ハッシュ/連想配列で整数を見ているかどうかを単純に集計できます。すでに数値が表示されている場合は、削除するか、または、まだ見られていない数値を新しい配列に移動して、元の配列のシフトを回避します。

Perlの場合:

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

答えを元の配列にする必要があるかどうかは明確ではありません。
ダグラスリーダー

新しい配列を必要とせずにこれを行うには、問題をその順序が重要であると指定していないので、複製を配列の最後からポップされた要素に置き換えるだけで、現在のループをやり直すことができます。これには追加の境界チェックが必要ですが、非常に実行可能です。
ジェフB

6
質問が編集されるまで、これは良い考えでした。あなたのハッシュテーブルの考えは明らかにルールに反しています。
WCWedin、2009年

14
この回答が最も多く投票される理由がわかりません。これはperlで書かれており、質問にあるように、Cでは利用できない重要な機能を使用しています。
LiraNuna、

5
質問は、PerlではなくCコードを要求しました。perlを使用すると、ハッシュテーブルを取得して無料で "プッシュ"できます。私がscalaでそれを行うことができたら、input.removeDuplicatesを呼び出すだけですが、それがインタビュアーに受け入れられたのではないかと思います:)
Peter Recore

5

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

外側のループの各反復は、配列の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;
}

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}
ユーリーチェルニ

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()コストを増加させません。
ダグラスリーダー

2

どれどれ:

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

それらが整数のみであることを考えると、簡単にするために、32ビットで最小/最大を探す必要がないと想定することができます。 (与えられた例の場合、かなりの最適化が許可されています)。そして、それらが64ビットである場合、最小値と最大値が使用しているメモリのビット数よりも離れていないことを知らないので、それは無関係です。
Steve Jessop、

理論は別として、512MBの割り当ては最小/最大を見つけるよりも時間がかかりませんか?
LiraNuna

そこにあるデータの量、および最小/最大が何であるかに依存します。512MBを超える入力を調べている場合は、余分なO(N)パスを回避する方がおそらく高速です。もちろん、これだけ多くの入力を見ている場合は、512MBの余裕がある可能性は低くなります。最小/最大が0 / INT_MAXに近い場合、最適化も役に立ちません。最初のステップは明らかに小さな数値に役立ちますが、このアルゴリズムが最悪の場合にUINT_MAXビットを使用するという事実を回避できないので、その制限を計画する必要があると私は言っています。
スティーブジェソップ

あなたは正しいかもしれません-いずれにせよ、質問の明確化はビット配列の使用が廃止されたことを意味します。誰かが後で制約を受けずに来て、可能なすべての回答を表示したい場合に備えて、この回答は残しておきます。
ダグラスリーダー

2

これは、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]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

シフトは要件の一部だと思いましたが、もちろんあなたは正しいです。
ドミニク

1
@mocj:私はあなたのソリューションが好きで、とてもエレガントに見えます。しかし、最後の2つの要素が等しい場合は、最後の1つ前に等しいかどうかのチェックを停止するため、機能しないと思います。(評判が高すぎて他のどこにもコメントできないため、ここに表示されます:()
Dominik

元の問題は配列の最後の値が無視できると述べていることを除いて、あなたは正しいです。変更された配列の長さを返さないため、2つの値が等しい場合、最後の値と最後から2番目の値の違いは重要ではありません。呼び出し元は返された配列の終わりをどこに解釈するのですか
mocj

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);

すべてを元の配列にコピーする前に、temp配列を宣言して要素をその配列に入れようとしています。


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 2015年

"<arr.length-1"のため@Sathesh No.
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)ストレージで実行できます。

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


2
元の質問の変更では、ハッシュテーブルは許可されていません。ただし、2つのポインターによるアプローチは、重複を特定した後で出力を圧縮するための良い方法です。
マークランサム

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


0

最初に、配列を作成します。check[n]ここで、nは重複をなくしたい配列の要素の数であり、(チェック配列の)すべての要素の値を1に設定します。forループを使用して、複製して、その名前がarrであると言い、forループでこれを書きます:

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

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


この質問では、追加のデータ構造を使用できません。
ejel 14

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

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.