web-dev-qa-db-ja.com

シャッフルされた連続整数の配列で重複する要素を見つける方法は?

私は最近どこかで質問に出会いました:

1001個の整数の配列があるとします。整数はランダムな順序ですが、各整数は1〜1000(両端を含む)の間であることがわかっています。また、各番号は、2回出現する1つの番号を除き、配列内に1回だけ表示されます。配列の各要素に一度しかアクセスできないと仮定します。繰り返し数を見つけるアルゴリズムを説明します。アルゴリズムで補助記憶装置を使用した場合、それを必要としないアルゴリズムを見つけることができますか?

私が知りたいのは、2番目の部分、つまり補助記憶装置を使用しないです。何かアイデアはありますか?

72
SysAdmin

それらをすべて加算し、そこから1001個の数字のみを使用した場合に予想される合計を減算します。

例えば:

Input: 1,2,3,2,4 => 12
Expected: 1,2,3,4 => 10

Input - Expected => 2
104
leppie

更新2:一部の人々は、XORを使用して重複した番号を見つけることはハックまたはトリックだと考えています。これに対する私の公式の応答は次のとおりです。重複する番号、ビットセットの配列で重複するパターンを探しています。そして、XORはビットセットを操作するためにADDよりも確実に適しています。 ":-)

更新:寝る前の楽しみのために、ここに追加のストレージ(ループカウンターでさえも)を必要とせず、各配列要素に1回だけ触れる非破壊的な「1行」の代替ソリューションがあります。そして、まったくスケーリングしません:-)

printf("Answer : %d\n",
           array[0] ^
           array[1] ^
           array[2] ^
           // continue typing...
           array[999] ^
           array[1000] ^
           1 ^
           2 ^
           // continue typing...
           999^
           1000
      );

コンパイラはコンパイル時に実際にその式の後半を計算するため、「アルゴリズム」は正確に1002回の操作で実行されることに注意してください。

また、配列要素の値がコンパイル時にもわかっている場合、コンパイラーはステートメント全体を定数に最適化します。 :-)

元のソリューション:正しい答えを見つけるために機能しているにもかかわらず、質問の厳しい要件を満たしていません。ループカウンターを保持するために1つの追加整数を使用し、各配列要素に3回アクセスします。2回目は現在の反復で読み取りと書き込みを行い、1回は次の反復で読み取ります。

さて、配列を通過するときに現在の要素のインデックスを保存するには、少なくとも1つの追加変数(またはCPUレジスタ)が必要です。

ただし、これとは別に、MAX_INTまでの任意のNに対して安全にスケーリングできる破壊アルゴリズムがあります。

for (int i = 1; i < 1001; i++)
{
   array[i] = array[i] ^ array[i-1] ^ i;
}

printf("Answer : %d\n", array[1000]);

これがなぜ機能するのかを理解するための演習は、簡単なヒントでお任せします:-):

a ^ a = 0
0 ^ a = a
77
Franci Penov

Franci Penovによるソリューションの非破壊バージョン。

これは、XOR演算子を使用して実行できます。

サイズ5の配列があるとしましょう:4, 3, 1, 2, 2
インデックスにあるのは:0, 1, 2, 3, 4

次に、すべての要素とすべてのインデックスのXORを実行します。 2を取得します。これは重複要素です。これは、0がXORで役割を果たさないために発生します。配列内の同じn-1要素と配列内のペアになっていない要素のみを持つ残りのn-1インデックスペアは重複します。

int i;
int dupe = 0;
for(i = 0; i < N; i++) {
    dupe = dupe ^ arr[i] ^ i;
}
// dupe has the duplicate.

このソリューションの最大の特徴は、追加ベースのソリューションで見られるオーバーフローの問題に悩まされないことです。

これはインタビューの質問なので、追加ベースのソリューションから始めて、オーバーフローの制限を特定し、XORベースのソリューション:)を与えるのが最善です。

これは追加の変数を使用するため、質問の要件を完全には満たしません。

22
codaddict

すべての数字を一緒に追加します。最終的な合計は、1 + 2 + ... + 1000+重複数になります。

15

フランシスペノフのソリューションを言い換えます。

(通常の)問題は:奇数回繰り返される1つの値を除き、偶数回繰り返される要素のみを含む任意の長さの整数の配列が与えられた場合、この値を見つけます。

解決策は次のとおりです。

_acc = 0
for i in array: acc = acc ^ i
_

あなたの現在の問題は適応です。秘Theは、2回繰り返される要素を見つけることであるため、この癖を補うためにソリューションを適応させる必要があります。

_acc = 0
for i in len(array): acc = acc ^ i ^ array[i]
_

配列全体を破壊しますが、フランシスのソリューションは最終的にこれを行います(ところで、最初または最後の要素しか破壊できませんでした...)

しかし、インデックス用に余分なストレージが必要なので、余分な整数も使用しても許されると思います...制限はおそらく、配列を使用できないようにするためです。

O(1)スペースが必要だった場合、より正確に表現されていました(ここでは任意なので1000はNとして見ることができます)。

6
Matthieu M.

すべての数字を追加します。整数の合計1..1000は(1000 * 1001)/ 2です。あなたが得るものとの違いはあなたの番号です。

5
kgiannakakis

1〜1000の正確な数字があることがわかっている場合は、結果を合計して、合計から_500500_(sum(1, 1000))を引くことができます。 sum(array) = sum(1, 1000) + repeated numberであるため、これは繰り返し数を与えます。

3
Justin Ardini

Pythonの1行ソリューション

arr = [1,3,2,4,2]
print reduce(lambda acc, (i, x): acc ^ i ^ x, enumerate(arr), 0)
# -> 2

なぜ機能するかについての説明は @ Matthieu M.'s answer にあります。

2
jfs

そう、これを行う非常に簡単な方法があります... 1から1000までの数字のそれぞれは、繰り返される数字を除いて1回だけ発生します。したがって、1 .... 1000からの合計は500500です。したがって、アルゴリズムは次のとおりです。

 sum = 0 
配列の各要素に対して:
 sum + =配列のその要素
 number_that_occurred_twice = sum-500500 

引数とコールスタックは補助ストレージとしてカウントされますか?

int sumRemaining(int* remaining, int count) {
    if (!count) {
        return 0;
    }
    return remaining[0] + sumRemaining(remaining + 1, count - 1);
}
printf("duplicate is %d", sumRemaining(array, 1001) - 500500);

編集:テールコールバージョン

int sumRemaining(int* remaining, int count, int sumSoFar) {
    if (!count) {
        return sumSoFar;
    }
    return sumRemaining(remaining + 1, count - 1, sumSoFar + remaining[0]);
}
printf("duplicate is %d", sumRemaining(array, 1001, 0) - 500500);
1
cobbal
public int duplicateNumber(int[] A) {
    int count = 0;
    for(int k = 0; k < A.Length; k++)
        count += A[k];
    return count - (A.Length * (A.Length - 1) >> 1);
}
1
Sunil B N
public static void main(String[] args) {
    int start = 1;
    int end = 10;
    int arr[] = {1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10};
    System.out.println(findDuplicate(arr, start, end));
}

static int findDuplicate(int arr[], int start, int end) {

    int sumAll = 0;
    for(int i = start; i <= end; i++) {
        sumAll += i;
    }
    System.out.println(sumAll);
    int sumArrElem = 0;
    for(int e : arr) {
        sumArrElem += e;
    }
    System.out.println(sumArrElem);
    return sumArrElem - sumAll;
}
1
mRaza
n = 1000
s = sum(GivenList)
r = str(n/2)
duplicate = int( r + r ) - s
1
Santhosh

追加のストレージ要件はありません(ループ変数を除く)。

int length = (sizeof array) / (sizeof array[0]);
for(int i = 1; i < length; i++) {
   array[0] += array[i];
}

printf(
    "Answer : %d\n",
    ( array[0] - (length * (length + 1)) / 2 )
);
1
N 1.1

三角形の数T(n)は、1からnまでのn個の自然数の合計です。n(n + 1)/ 2として表すことができます。自然数、1つだけの数字が複製される場合、指定されたすべての数字を簡単に合計し、T(1000)を引くことができます。結果にはこの複製が含まれます。

三角形の数T(n)の場合、nが10の累乗の場合、10進数表現に基づいてこのT(n)を見つける美しい方法もあります。

n = 1000
s = sum(GivenList)
r = str(n/2)
duplicate = int( r + r ) - s
0
psihodelia

連続値のXORのプロパティに基づくFraciの回答の改善:

int result = xor_sum(N);
for (i = 0; i < N+1; i++)
{
   result = result ^ array[i];
}

どこで:

// Compute (((1 xor 2) xor 3) .. xor value)
int xor_sum(int value)
{
    int modulo = x % 4;
    if (modulo == 0)
        return value;
    else if (modulo == 1)
        return 1;
    else if (modulo == 2)
        return i + 1;
    else
        return 0;
}

または、擬似コード/数学言語でf(n)として定義(最適化):

if n mod 4 = 0 then X = n
if n mod 4 = 1 then X = 1
if n mod 4 = 2 then X = n+1
if n mod 4 = 3 then X = 0

そして、標準形式f(n)は次のとおりです。

f(0) = 0
f(n) = f(n-1) xor n
0

Auxバージョンでは、最初にすべての値を-1に設定し、繰り返しながら、aux配列に値が既に挿入されているかどうかを確認します。そうでない場合(値は-1でなければなりません)、挿入します。重複している場合、ここにあなたの解決策があります!

Auxのないものでは、リストから要素を取得し、リストの残りにその値が含まれているかどうかを確認します。含まれている場合、ここで見つけました。

private static int findDuplicated(int[] array) {
    if (array == null || array.length < 2) {
        System.out.println("invalid");
        return -1;
    }
    int[] checker = new int[array.length];
    Arrays.fill(checker, -1);
    for (int i = 0; i < array.length; i++) {
        int value = array[i];
        int checked = checker[value];
        if (checked == -1) {
            checker[value] = value;
        } else {
            return value;
        }
    }
    return -1;
}

private static int findDuplicatedWithoutAux(int[] array) {
    if (array == null || array.length < 2) {
        System.out.println("invalid");
        return -1;
    }
    for (int i = 0; i < array.length; i++) {
        int value = array[i];
        for (int j = i + 1; j < array.length; j++) {
            int toCompare = array[j];
            if (value == toCompare) {
                return array[i];
            }
        }
    }
    return -1;
}
0
user3743369

質問2に対する私の答え:

1-(to)Nからの数の和と積、たとえばSUMPRODを見つけます。

1-N- x -yの数値の合計と積を見つけます(x、yが欠落していると仮定します)。たとえば、mySum、myProd、

したがって:

SUM = mySum + x + y;
PROD = myProd* x*y;

したがって:

x*y = PROD/myProd; x+y = SUM - mySum;

この方程式を解くと、x、yを見つけることができます。

0
Zhixi Chen

すべての要素の追加をサポートし、そこからすべてのインデックスの合計を減算しますが、要素の数が非常に多い場合、これは機能しません。つまり整数オーバーフローが発生します!そのため、整数オーバーフローの可能性を大幅に減らすことができるこのアルゴリズムを考案しました。

   for i=0 to n-1
        begin:  
              diff = a[i]-i;
              dup = dup + diff;
        end
   // where dup is the duplicate element..

しかし、この方法では、重複する要素が存在するインデックスを見つけることができません!

そのため、もう一度アレイを走査する必要がありますが、これは望ましくありません。

0
Poulami