回答:
これは私が思いついたものであり、追加の符号ビットを必要としません:
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)
時間内に実行されます。スワップが発生するのは、i
そのようなが存在する場合のみA[i] != i
であり、各スワップは、そのようなのような要素を少なくとも1つ設定しA[i] == i
ます。これは、スワップの合計数(つまり、while
ループ本体の実行の合計数)が最大でであることを意味しN-1
ます。
2番目のループは、等しくないx
for の値を出力します。最初のループは、配列内に少なくとも1つ存在する場合、それらのインスタンスの1つがにあることを保証するため、これには、存在しない値が出力されます。配列。A[x]
x
x
A[x]
x
5
は範囲内にないため0..N-1
(N
この場合は5
)、これは有効な入力ではありません。
print
ステートメントを変更してprint i
これをstackoverflow.com/questions/5249985/…の解決策に変えることと、(「bag」が変更可能な配列であると仮定して)stackoverflow.com/questions/3492302/…の Qk について簡単に触れます。
cafのすばらしい答えは、配列に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
が2回以上出現する場合、それらの出現の1つが正しい位置にあることが保証されるという特性を利用しますA[m]
。注意が必要な場合は、その「ホーム」ロケーションを使用して、重複がまだ印刷されているかどうかに関する情報を保存できます。
cafのバージョンでは、配列をA[i] != i
調べたときに、それA[i]
が重複していることを暗示していました。私のバージョンでは、わずかに異なる不変式に依存しています。これは、これまでに見たことのない重複であることをA[i] != i && A[A[i]] == A[i]
意味しています。(「これまでに見たことがない」部分をドロップすると、残りはcafの不変条件の真実と、すべての重複がホームロケーションにコピーを持っていることが保証されることによって暗示されることがわかります。)このプロパティは、最初に(cafの最初のループが終了した後)と、各ステップの後で維持されることを以下に示します。A[i]
配列を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
前半がtrueであることがわかったとしても、後半は、それが指している場所がホームの場所であるかどうかをテストし、そうでないことがわかります。この状況では、重複した値であるかどうかはわかりませんが、どちらの方法でもそれがわかります。if
A[i] != i
m
A[m]
これらの2サイクルはcafの最初のループの結果に表示されないことが保証されているため、すでに報告されています。(注場合にm != A[m]
、その後、正確の1 m
とは、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
-
て~
、ゼロの問題のために。
O(n)
隠れたスペース、つまりn
符号ビットを使用しています。アレイは、各要素の間の値をのみ保持することができるように定義されている場合0
とn-1
、それは明らかに仕事をしません。
比較的小さい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;
}
}
}
if (value > i) a[i--] = a[value];
作品を次の場合value <= i
、我々はすでにで値を処理しているa[value]
し、安全にそれを上書きすることができます。また、O(N)の性質が明白であるとは言えません!綴り:メインループの実行N
回数、およびa[i--] = a[value];
行の実行回数。その行が実行できるのはa[value] < N
、であり、実行するたびに、直後にまだN
設定されていない配列の値がに設定されN
ているN
ため、合計で最大で2N
ループを繰り返し実行できるためです。
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)時間と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)空間。
アルゴリズムは、次の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');
}
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(n)時間の複雑さと一定の余分なスペースで重複を見つけるために、迅速に1つのサンプルプレイグラウンドアプリを作成しました。URL Finding Duplicatesを確認してください
IMP上記のソリューションは、配列に0からn-1までの要素が含まれ、これらの数値のいずれかが何度も現れる場合に機能しました。
private static void printRepeating(int arr[], int size) {
int i = 0;
int j = 1;
while (i < (size - 1)) {
if (arr[i] == arr[j]) {
System.out.println(arr[i] + " repeated at index " + j);
j = size;
}
j++;
if (j >= (size - 1)) {
i++;
j = i + 1;
}
}
}
配列が大きすぎない場合、このソリューションはより簡単です。ティックするために同じサイズの別の配列を作成します。
1入力配列と同じサイズのビットマップ/配列を作成します
int check_list[SIZE_OF_INPUT];
for(n elements in checklist)
check_list[i]=0; //initialize to zero
2入力配列をスキャンし、上記の配列のカウントをインクリメントします
for(i=0;i<n;i++) // every element in input array
{
check_list[a[i]]++; //increment its count
}
3ここで、check_list配列をスキャンして、複製を1回または複数回印刷します
for(i=0;i<n;i++)
{
if(check_list[i]>1) // appeared as duplicate
{
printf(" ",i);
}
}
もちろん、上記のソリューションで消費されるスペースの2倍かかりますが、時間効率は基本的にO(n)であるO(2n)です。
O(1)
スペースではありません。
a[a[i]]
制約は、解決策に少しの手掛かりを与えます-すべての有効な配列値は、有効な配列インデックスのヒントでもあり、O(1)スペース制約は、swap()
操作がキーであることを示唆しています。