web-dev-qa-db-ja.com

O(n) timeおよびO(1) spaceで重複を見つける

入力:0からn-1までの要素を含むn個の要素の配列が与えられ、これらの数字は何度でも現れる。

目標:O(n))でこれらの繰り返し数を見つけ、一定のメモリ空間のみを使用する。

たとえば、nを7、arrayを{1、2、3、1、3、0、6}とすると、答えは1と3になります。ここで同様の質問を確認しましたが、答えはHashSetなど.

同じための効率的なアルゴリズムはありますか?

119
Zaki

これは私が思いついたものであり、追加の符号ビットを必要としません:

_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が少なくとも1回存在する場合、それらのエントリの1つが_A[x]_の位置になるように配列を並べ替えます。

最初の赤面ではO(n)に見えないかもしれませんが、ネストループはありますが、O(N)時間で実行されます。スワップのみ_A[i] != i_などのiがあり、各スワップが_A[i] == i_などの少なくとも1つの要素を設定する場合に発生します(以前はそうでなかった)。スワップ(したがって、whileループ本体の実行の総数)は、最大_N-1_です。

2番目のループは、xの値を出力します。これは_A[x]_がxと等しくありません-最初のループは、xが少なくとも1回配列、それらのインスタンスの1つは_A[x]_にあります。これは、配列に存在しないxの値を出力することを意味します。

(あなたがそれで遊ぶことができるように、理想的なリンク)

162
caf

caf's brilliant answer は、配列にk回現れる各数値をk-1回印刷します。これは便利な動作ですが、質問では間違いなく各複製を1回だけ印刷する必要があり、彼は線形の時間/一定のスペースの境界を破ることなくこれを行う可能性を暗示しています。これは、2番目のループを次の擬似コードに置き換えることで実行できます。

for (i = 0; i < N; ++i) {
    if (A[i] != i && A[A[i]] == A[i]) {
        print A[i];
        A[A[i]] = i;
    }
}

これは、最初のループが実行された後に、値mが複数回出現する場合、それらの外観の1つが正しい位置、つまりA[m]。注意が必要な場合は、その「ホーム」ロケーションを使用して、重複がまだ印刷されているかどうかに関する情報を保存できます。

Cafのバージョンでは、配列を調べたときに、A[i] != iA[i]は重複しています。私のバージョンでは、わずかに異なる不変式に依存しています:A[i] != i && A[A[i]] == A[i] ことを意味します A[i]は、これまで見たことのない複製です。 (「前に見たことのない」部分をドロップすると、残りはcafの不変量の真理と、すべての重複がホームロケーションに何らかのコピーを持っているという保証によって暗示されていることがわかります。)最初(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?はい、そうなります。なぜなら、この新しいiif条件の前半、A[i] != iはtrueであり、後半はそれが指す場所がホームの場所であるかどうかをテストし、そうでないことを見つけます。この状況では、mまたはA[m]は重複した値でしたが、どちらの方法でも既に報告されていることがわかります。これらの2サイクルは、 cafの最初のループの結果。 (もしm != A[m]その後、mおよびA[m]は複数回出現し、もう一方はまったく出現しません。

33
j_random_hacker

これが擬似コードです

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

C++のサンプルコード

22
Prasoon Saurav

比較的小さな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++ではなく、とにかく

http://ideone.com/GRZPI

2
hoha

あまりきれいではありませんが、少なくとも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;
    }
  }
}
1
CAFxX

この配列を単方向のグラフデータ構造として提示すると仮定します。各数値は頂点であり、配列内のインデックスはグラフのエッジを形成する別の頂点を指します。

さらに簡単にするために、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) time and O(1) space。

1
Ivan Voroshilin

Cの1つのソリューションは次のとおりです。

#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) timeおよびO(1)スペースの複雑さ。

1
Anshul garg

Swiftで重複を見つけるためのサンプルプレイグラウンドアプリを1つ作成しました0(n)時間の複雑さと一定の余分なスペース。URLを確認してください 重複の検索

[〜#〜] imp [〜#〜]上記の解決策は、配列に0〜n-1の要素が含まれ、これらの数値のいずれかが表示される場合に機能しました。何回でも。

0
CrazyPro007

アルゴリズムは、次のC関数で簡単に確認できます。元の配列の取得は、必須ではありませんが、各エントリをモジュロnで取得することができます。

void print_repeats(unsigned a[], unsigned n)
{
    unsigned i, _2n = 2*n;
    for(i = 0; i < n; ++i) if(a[a[i] % n] < _2n) a[a[i] % n] += n;
    for(i = 0; i < n; ++i) if(a[i] >= _2n) printf("%u ", i);
    putchar('\n');
}

テスト用のIdeoneリンク。

0
Apshir

上記のcafの方法を示す小さなpythonコード:

a = [3, 1, 1, 0, 4, 4, 6] 
n = len(a)
for i in range(0,n):
    if a[ a[i] ] != a[i]: a[a[i]], a[i] = a[i], a[a[i]]
for i in range(0,n):
    if a[i] != i: print( a[i] )
0
vine'th
static void findrepeat()
{
    int[] arr = new int[7] {0,2,1,0,0,4,4};

    for (int i = 0; i < arr.Length; i++)
    {
        if (i != arr[i])
        {
            if (arr[i] == arr[arr[i]])
            {
                Console.WriteLine(arr[i] + "!!!");
            }

            int t = arr[i];
            arr[i] = arr[arr[i]];
            arr[t] = t;
        }
    }

    for (int j = 0; j < arr.Length; j++)
    {
        Console.Write(arr[j] + " ");
    }
    Console.WriteLine();

    for (int j = 0; j < arr.Length; j++)
    {
        if (j == arr[j])
        {
            arr[j] = 1;
        }
        else
        {
            arr[arr[j]]++;
            arr[j] = 0;
        }
    }

    for (int j = 0; j < arr.Length; j++)
    {
        Console.Write(arr[j] + " ");
    }
    Console.WriteLine();
}
0
Eli