web-dev-qa-db-ja.com

並べ替えなしで、配列内の2つの繰り返し数を見つけるアルゴリズム

サイズn(数値は0とn-3の間)の配列があり、2つの数値のみが繰り返されます。要素は配列にランダムに配置されます。

例えば。 {2、3、6、1、5、4、0、3、5}でn = 9、繰り返し数は3と5です。

繰り返し数を見つける最良の方法は何ですか?

追伸[並べ替えは使用しないでください]

26
Aman Jain

入力の可能なドメインが何かがわかっている場合は、O(n)ソリューションがあります。たとえば、入力配列に0〜100の数値が含まれている場合は、次のコードを検討してください。

bool flags[100];
for(int i = 0; i < 100; i++)
    flags[i] = false;

for(int i = 0; i < input_size; i++)
    if(flags[input_array[i]])
         return input_array[i];
    else       
        flags[input_array[i]] = true;

もちろん追加のメモリがありますが、これが最速です。

27
Sesh

OK、私はただそれを休めることができないようです:)

最も簡単なソリューション

int A[N] = {...};

int signed_1(n) { return n%2<1 ? +n : -n;  } // 0,-1,+2,-3,+4,-5,+6,-7,...
int signed_2(n) { return n%4<2 ? +n : -n;  } // 0,+1,-2,-3,+4,+5,-6,-7,...

long S1 = 0;  // or int64, or long long, or some user-defined class
long S2 = 0;  // so that it has enough bits to contain sum without overflow

for (int i=0; i<N-2; ++i)
{
   S1 += signed_1(A[i]) - signed_1(i);
   S2 += signed_2(A[i]) - signed_2(i);
} 

for (int i=N-2; i<N; ++i)
{
   S1 += signed_1(A[i]);
   S2 += signed_2(A[i]);
} 

S1 = abs(S1);
S2 = abs(S2);

assert(S1 != S2);  // this algorithm fails in this case

p = (S1+S2)/2;
q = abs(S1-S2)/2;

1つの合計(S1またはS2)には同じ符号のpとqが含まれ、もう1つの合計-反対の符号では、他のすべてのメンバーが削除されます。
S1とS2には合計に対応するのに十分なビットが必要です。abs()。のため、アルゴリズムはオーバーフローを表しません。

abs(S1)== abs(S2)の場合、アルゴリズムは失敗しますが、この値は引き続きpとqの差になります(つまり、abs(p-q)== abs(S1))。

以前の解決策

誰かがフィールドでそのような問題に遭遇することはないだろうと思います;)
そして、私はおそらく、先生の期待を知っています。

配列{0,1,2、...、n-2、n-1}を取りましょう
与えられたものは、最後の2つの要素n-2とn-1を未知のpとq(次数が少ない)で置き換えることによって生成できます。

したがって、要素の合計は(n-1)n/2 + p + q-(n-2)-(n-1)
二乗和(n-1)n(2n-1)/ 6 + p ^ 2 + q ^ 2-(n-2)^ 2-(n-1)^ 2

単純な数学は残っています:

  (1)  p+q = S1  
  (2)  p^2+q^2 = S2

数学のクラスが二乗方程式を解くことを教えるので、あなたは確かにそれを解かないでしょう。

まず、2 ^ 32を法としてすべてを計算します。つまり、オーバーフローを考慮します。
次に、ペア{p、q}をチェックします:{0、S1}、{1、S1-1} ...式(2)と照合して候補を見つけます(モジュロと2乗により2を超える数)
最後に、見つかった候補が本当に配列に2回存在するかどうかを確認します。

21
eugensk00

配列には0からn-3までのすべての数と2つの繰り返し数(p&q)が含まれています。簡単にするために、ここでは0ケースを無視してみましょう。

配列全体の合計と積を計算すると、次のようになります。

1 + 2 + ... + n-3 + p + q = p + q + (n-3)(n-2)/2

したがって、配列全体の合計から(n-3)(n-2)/ 2を引くと、

sum(Array) - (n-3)(n-2)/2 = x = p + q

次に、製品に対して同じことを行います。

1 * 2 * ... * n - 3 * p * q = (n - 3)! * p * q

prod(Array) / (n - 3)! = y = p * q

あなたは今これらの用語を得ました:

x = p + q

y = p * q

=> y(p + q) = x(p * q)

この項を変換すると、pとqを計算できるはずです。

12
sdfx

各要素をセット/ハッシュテーブルに挿入します。まず、要素がすでにそこにあるかどうかを確認します。

7
tjdonaldson

Sum(array)=(n-2)*(n-3)/ 2 + 2つの欠落した数値という事実を利用できるかもしれません。

編集:他の人が指摘したように、二乗和と組み合わせて、これを使用することができます。

7
Eclipse

トピックに関するこの古くて良い論文をチェックしてください:

6
CMS

質問に対するいくつかの回答: 配列にn…n + m?が含まれているかどうかを判断するアルゴリズム 目的に合わせて採用できる副問題の解として含まれています。

たとえば、これは my answer の関連部分です。

bool has_duplicates(int* a, int m, int n)
{
  /** O(m) in time, O(1) in space (for 'typeof(m) == typeof(*a) == int')

      Whether a[] array has duplicates.

      precondition: all values are in [n, n+m) range.

      feature: It marks visited items using a sign bit.
  */
  assert((INT_MIN - (INT_MIN - 1)) == 1); // check n == INT_MIN
  for (int *p = a; p != &a[m]; ++p) {
    *p -= (n - 1); // [n, n+m) -> [1, m+1)
    assert(*p > 0);
  }

  // determine: are there duplicates
  bool has_dups = false;
  for (int i = 0; i < m; ++i) {
    const int j = abs(a[i]) - 1;
    assert(j >= 0);
    assert(j < m);
    if (a[j] > 0)
      a[j] *= -1; // mark
    else { // already seen
      has_dups = true;
      break;
    }
  }

  // restore the array
  for (int *p = a; p != &a[m]; ++p) {
    if (*p < 0) 
      *p *= -1; // unmark
    // [1, m+1) -> [n, n+m)
    *p += (n - 1);        
  }

  return has_dups; 
}

プログラムは配列を変更しません(配列は書き込み可能でなければなりませんが、その値は終了時に復元されます)。

INT_MAXまでの配列サイズで機能します(64ビットシステムでは9223372036854775807)。

3
jfs
配列が
 
 a [0]、a [1]、a [2] ..... a [n-1] 
 
 sumA = a [0] + a [1] + .... + a [n-1] 
 sumASquare = a [0] * a [0] + a [1] * a [1] + a [ 2] * a [2] + .... + a [n] * a [n] 
 
 sumFirstN =(N *(N + 1))/ 2ここで、N = n- 3 so 
 sumFirstN =(n-3)(n-2)/ 2 
 
同様に
 
 sumFirstNSquare = N *(N + 1 )*(2 * N + 1)/ 6 =(n-3)(n-2)(2n-5)/ 6 
 
繰り返し要素が= XとY [.____であるとします。] 
 so X + Y = sumA-sumFirstN; 
 X * X + Y * Y = sumASquare-sumFirstNSquare; 
 
この2次式を解くと、 XとYの値を取得します。
時間の複雑度= O(n)
空間の複雑度= O(1)
2
GG.

質問が非常に古いことはわかっていますが、突然それにぶつかり、興味深い答えが得られたと思います。私たちは、これがどれほど優れた退屈なものであっても、ブレインティーザーであり、簡単な解決策(つまり、HashMap、Sortなど)であることを知っています。

数値は整数であるため、ビットサイズは一定(つまり32)です。現在4ビット整数で作業していると仮定します。 [〜#〜] a [〜#〜][〜#〜] b [〜#〜]を探します。これらは重複した数値です。

それぞれ1ビットに4つのバケットが必要です。各バケットには、特定のビットが1である数値が含まれています。たとえば、バケット1は2、3、4、7、...を取得します。

Bucket 0 : Sum ( x where: x & 2 power 0 == 0 )
...
Bucket i : Sum ( x where: x & 2 power i == 0 )

重複がない場合、各バケットの合計はどうなるかがわかります。私はこれを事前知識と考えています。

上記のバケットが生成されると、それらの束は予想以上の値を持つことになります。バケットから数を構成することで、(情報としてA OR B)になります。

(A XOR B)は次のように計算できます。

A XOR B = Array[i] XOR Array[i-1] XOR ... 0, XOR n-3 XOR n-2  ... XOR 0

バケットに戻ると、どちらのバケットに番号があり、どちらのバケットに1つしかないかが正確にわかります(XORビットから)。

数値が1つしかないバケットの場合、数値num =(合計-バケットの予想合計)を抽出できます。ただし、重複する数値の1つを見つけることができる場合にのみ、問題はありません。したがって、A XOR Bに少なくとも1つのビットがある場合、答えがわかります。

しかし、A XOR Bがゼロの場合はどうなりますか?まあ、このケースは、両方の重複する番号が同じ番号である場合にのみ可能です。この番号がA OR Bの答えになります。

2
naiem

18に答えてください。9の配列を取り、要素は0から始まります。したがって、配列内の最大要素は6になります。 0から6までの要素の合計を取り、配列要素の合計を取ります。それらの差を計算します(dなど)。これはp + qです。 XOR要素の0から6まで(たとえばx1)を取ります。XOR配列要素(たとえばx2)を取ります)x2はXOR 0から6までのすべての要素。ただし、2つの繰り返し要素は相互に打ち消し合うため、繰り返されます。ここで、i = 0〜6の場合、配列の各要素について、pはその要素a [i]であるため、 qこのeleをdから差し引くことにより、do XOR of p and q and XOR them with x2 and check if x1 == x2。同様にすべてのelementsこの条件が真になる要素を取得し、O(n)で完了します。コーディングを続けてください!

1
Minal

範囲を指定しているため、基数ソートができます。これは配列をO(n)でソートします。ソートされた配列で重複を検索すると、O(n)になります

1
Ravi

単純なネストされたforループを使用できます

 int[] numArray = new int[] { 1, 2, 3, 4, 5, 7, 8, 3, 7 };

        for (int i = 0; i < numArray.Length; i++)
        {
            for (int j = i + 1; j < numArray.Length; j++)
            {
                if (numArray[i] == numArray[j])
                {
                   //DO SOMETHING
                }
            }

* OR出現回数を取得する場合は、配列をフィルタリングして再帰関数を使用できます*

int[] array = { 1, 2, 3, 4, 5, 4, 4, 1, 8, 9, 23, 4, 6, 8, 9, 1,4 };
int[] myNewArray = null;
int a = 1;

 void GetDuplicates(int[] array)
    for (int i = 0; i < array.Length; i++)
            {
                for (int j = i + 1; j < array.Length; j++)
                {
                    if (array[i] == array[j])
                    {
                          a += 1;
                    }
                }
                Console.WriteLine(" {0} occurred {1} time/s", array[i], a);

                IEnumerable<int> num = from n in array where n != array[i] select n;
                 myNewArray = null;
                 a = 1;
                 myNewArray = num.ToArray() ;

                 break;

            }
             GetDuplicates(myNewArray);
1
Tarek Fouda

これをチェックしてください... O(n)時間とO(1)スペースの複雑さ

 for(i=0;i< n;i++)
 xor=xor^arr[i]
 for(i=1;i<=n-3;i++)
 xor=xor^i;

したがって、与えられた例では、3と5のxorを取得します

xor=xor & -xor  //Isolate the last digit

for(i = 0; i < n; i++)
{
if(arr[i] & xor)
  x = x ^ arr[i]; 
else
  y = y ^ arr[i]; 
}
for(i = 1; i <= n-3; i++)
{
if(i & xor)
  x = x ^ i; 
else
  y = y ^ i; 

}

xとyはあなたの答えです

1
Sree Ram

配列を並べ替えることが最善の解決策のようです。単純なソートを行うと、検索が簡単になり、時間/スペースが大幅に短縮されます。

それ以外の場合で、数値のドメインがわかっている場合は、その数のバケットを含む配列を作成し、配列を進むたびにそれぞれを増分します。このようなもの:

int count [10];

for (int i = 0; i < arraylen; i++) {
    count[array[i]]++;
}

次に、1より大きい数値を配列で検索します。これらは重複している項目です。元の配列全体で1つのパスとカウント配列全体で1つのパスのみが必要です。

1
Steve Rowe

モジュラー演算を使用しないPython of @ eugensk の回答(そのリビジョンの1つ)での実装は次のとおりです。これはsingle-passアルゴリズム、O(log(n))in space。固定幅の場合(例32ビット)整数が使用され、必要な固定幅の数値は2つだけです(32ビットの場合:64ビットの数値と128ビットの数値の1つ)。任意の大きな整数シーケンスを処理できます(1つの整数をしたがって、シーケンス全体がメモリにある必要はありません)。

def two_repeated(iterable):
    s1, s2 = 0, 0
    for i, j in enumerate(iterable):
        s1 += j - i     # number_of_digits(s1) ~ 2 * number_of_digits(i)
        s2 += j*j - i*i # number_of_digits(s2) ~ 4 * number_of_digits(i) 
    s1 += (i - 1) + i
    s2 += (i - 1)**2 + i**2

    p = (s1 - int((2*s2 - s1**2)**.5)) // 2 
    # `Decimal().sqrt()` could replace `int()**.5` for really large integers
    # or any function to compute integer square root
    return p, s1 - p

例:

>>> two_repeated([2, 3, 6, 1, 5, 4, 0, 3, 5])
(3, 5)

上記のコードのより詳細なバージョンの後に説明があります。

def two_repeated_seq(arr):
    """Return the only two duplicates from `arr`.

    >>> two_repeated_seq([2, 3, 6, 1, 5, 4, 0, 3, 5])
    (3, 5)
    """
    n = len(arr)
    assert all(0 <= i < n - 2 for i in arr) # all in range [0, n-2)
    assert len(set(arr)) == (n - 2) # number of unique items

    s1 = (n-2) + (n-1)       # s1 and s2 have ~ 2*(k+1) and 4*(k+1) digits  
    s2 = (n-2)**2 + (n-1)**2 # where k is a number of digits in `max(arr)`
    for i, j in enumerate(arr):
        s1 += j - i     
        s2 += j*j - i*i

    """
    s1 = (n-2) + (n-1) + sum(arr) - sum(range(n))
       = sum(arr) - sum(range(n-2))
       = sum(range(n-2)) + p + q - sum(range(n-2))
       = p + q
    """
    assert s1 == (sum(arr) - sum(range(n-2)))

    """
    s2 = (n-2)**2 + (n-1)**2 + sum(i*i for i in arr) - sum(i*i for i in range(n))
       = sum(i*i for i in arr) - sum(i*i for i in range(n-2))
       = p*p + q*q
    """
    assert s2 == (sum(i*i for i in arr) - sum(i*i for i in range(n-2)))

    """
    s1 = p+q
    -> s1**2 = (p+q)**2
    -> s1**2 = p*p + 2*p*q + q*q
    -> s1**2 - (p*p + q*q) = 2*p*q
    s2 = p*p + q*q
    -> p*q = (s1**2 - s2)/2

    Let C = p*q = (s1**2 - s2)/2 and B = p+q = s1 then from Viete theorem follows
    that p and q are roots of x**2 - B*x + C = 0
    -> p = (B + sqrtD) / 2
    -> q = (B - sqrtD) / 2
    where sqrtD = sqrt(B**2 - 4*C)

    -> p = (s1 + sqrt(2*s2 - s1**2))/2
    """
    sqrtD = (2*s2 - s1**2)**.5
    assert int(sqrtD)**2 == (2*s2 - s1**2) # perfect square
    sqrtD = int(sqrtD)
    assert (s1 - sqrtD) % 2 == 0 # even
    p = (s1 - sqrtD) // 2
    q = s1 - p
    assert q == ((s1 + sqrtD) // 2)
    assert sqrtD == (q - p)
    return p, q

注:数値の整数平方根(〜N ** 4)を計算すると、上記のアルゴリズムは非線形になります。

1
jfs

C:

_    int arr[] = {2, 3, 6, 1, 5, 4, 0, 3, 5};

    int num = 0, i;

    for (i=0; i < 8; i++)
         num = num ^ arr[i] ^i;
_

_x^x=0_以降、奇数回繰り返される数値は無効化されます。一意の番号をaとbと呼びましょう。_a^b_が残ります。 _a^b != 0_以来、私たちは_a != b_を知っています。 _a^b_の任意の1ビットを選択し、それをマスクとして使用します。つまり、xを2の累乗として選択し、x & (a^b)がゼロ以外になるようにします。

次に、リストを2つのサブリストに分割します。1つのサブリストには、_y&x == 0_を含むすべての数値yが含まれ、残りは他のサブリストに入ります。 xを選択したところで、aとbのペアが異なるバケットにあることがわかります。これで、上記と同じ方法を各バケットに個別に適用して、aとbが何であるかを確認できます。

0

これはどう:

for (i=0; i<n-1; i++) {
  for (j=i+1; j<n; j++) {
    if (a[i] == a[j]) {
        printf("%d appears more than once\n",a[i]);
        break;
    }
  }
}

確かに最速ではありませんが、シンプルで理解しやすく、追加のメモリは必要ありません。 nが9または100のような小さい数である場合、それは「最良」である可能性があります。 (つまり、「最高」とは、実行が最も速い、メモリフットプリントが最小、保守性が最も高い、開発コストが最小など、さまざまなことを意味します。)

0
vulcan

繰り返されていない要素の数を見つける小さなプログラムを作成しました。これを調べて、あなたの意見を教えてください。現時点では、偶数の要素は偶数であると想定していますが、奇数にも簡単に拡張できます。

したがって、私の考えは、最初に数値を並べ替え、次に私のアルゴリズムを適用することです。クイック並べ替えを使用して、この要素を並べ替えることができます。

以下のように入力配列を取ってみましょう

int arr[] = {1,1,2,10,3,3,4,5,5,6,6};

番号2、10、4は繰り返されませんが、並べ替えられています。並べ替えられていない場合は、クイックソートを使用して最初に並べ替えます。

これに私のプログラムを適用しましょう

using namespace std;

main()
{
    //int arr[] = {2, 9, 6, 1, 1, 4, 2, 3, 5};
    int arr[] = {1,1,2,10,3,3,4,5,5,6,6};

    int i = 0;

    vector<int> vec;

    int var = arr[0];
    for(i = 1 ; i < sizeof(arr)/sizeof(arr[0]); i += 2)
    {
            var = var ^ arr[i];

            if(var != 0 )
            {
                //put in vector
                var = arr[i-1];
                vec.Push_back(var);
                i = i-1;
            }
            var = arr[i+1];
    }

    for(int i = 0 ; i < vec.size() ; i++)
        printf("value not repeated = %d\n",vec[i]);

}

これは出力を与えます:

value not repeated= 2

value not repeated= 10

value not repeated= 4

そのシンプルで非常に単純な、XOR man。

0
Yusuf Khan

以下は、順序統計を使用し、O(n)で実行されるアルゴリズムです。

これを解決するには、中央値をパラメーターとしてSELECTを繰り返し呼び出します。

また、SELECTの呼び出し後、中央値以下の要素が中央値の左側に移動することにも依存しています。

  • 中央値をパラメーターとしてSELECTAを呼び出します。
  • 中央値がfloor(n/2)の場合、繰り返される値は中央値に対して正しいです。したがって、配列の右半分を続行します。
  • そうでない場合は、繰り返し値は中央値に任されます。したがって、配列の左半分を続行します。
  • この方法を再帰的に続けます。

例えば:

  • _A={2, 3, 6, 1, 5, 4, 0, 3, 5}_ _n=9_の場合、中央値は_4_の値でなければなりません。
  • SELECTへの最初の呼び出しの後
  • _A={3, 2, 0, 1, <3>, 4, 5, 6, 5}_中央値は_4_よりも小さいので、左半分から続けます。
  • _A={3, 2, 0, 1, 3}_
  • SELECTへの2回目の呼び出しの後
  • _A={1, 0, <2>, 3, 3}_の場合、中央値は_2_である必要があります。そのため、右半分から続行します。
  • _A={3, 3}_、見つかりました。

このアルゴリズムはO(n+n/2+n/4+...)=O(n)で実行されます。

0
Avi Cohen
for(i=1;i<=n;i++) {
  if(!(arr[i] ^ arr[i+1]))
        printf("Found Repeated number %5d",arr[i]);
}
0
srinunaik

番号ごとに、それが配列の残りの部分に存在するかどうかを確認します。

0
mookid8000

並べ替えを行わないと、既にアクセスした番号を追跡できます。

疑似コードではこれは基本的に(この方法で行われるので、私はあなたに答えを与えるだけではありません):

for each number in the list
   if number not already in unique numbers list
      add it to the unique numbers list
   else
      return that number as it is a duplicate
   end if
end for each
0
mezoid

https://en.wikipedia.org/wiki/HyperLogLog の使用についてはどうですか?

Redisが行う http://redis.io/topics/data-types-intro#hyperloglogs

HyperLogLogは、固有のものをカウントするために使用される確率的データ構造です(技術的には、これはセットのカーディナリティの推定と呼ばれます)。通常、一意のアイテムを数えるには、数えたいアイテムの数に比例したメモリ量を使用する必要があります。複数回カウントすることを避けるために、過去に見た要素を覚えておく必要があるためです。ただし、メモリと精度をトレードオフする一連のアルゴリズムがあります。Redis実装の場合、1%未満である標準誤差の推定測度で終了します。このアルゴリズムの魔法は、カウントされたアイテムの数に比例したメモリ量を使用する必要がなくなり、代わりに一定量のメモリを使用できることです。最悪の場合は12kバイト、HyperLogLog(今後はHLLと呼ぶ)で要素がほとんど見られない場合ははるかに少なくなります。

0
brutuscat