web-dev-qa-db-ja.com

配列がO(n)の順列であるかどうかを見分ける方法は?

入力:1からNまでの整数値を含むN要素の読み取り専用配列(一部の整数値は複数回表示される場合があります!)。そして、固定サイズのメモリゾーン(10、100、1000など-not Nによって異なります)。

配列が順列を表すかどうかをO(n)で見分ける方法は?

-私がこれまでに達成したこと(答えはこれがない良いことを証明した):-

  1. 限られたメモリ領域を使用して、配列の合計と積を格納します。
  2. 合計をN *(N + 1)/ 2と比較し、積をN!と比較します。

条件(2)が真の場合、I might順列があることを私は知っています。条件(2)が順列があるかどうかを判断するのに十分であることを証明する方法があるかどうか疑問に思っています。今のところ私はこれを理解していません...

39
INS

私は解決策があることに少し懐疑的です。あなたの問題は、数年前に数学の文献で提起された問題に非常に近いようです。 ここに要約を示します(「重複検出問題」、S。KamalAbdali、2003) 循環検出を使用します-アイデアは次のとおりです。

重複がある場合、1とNの間に数jが存在するため、次のように無限ループが発生します。

x := j;
do
{
   x := a[x];
}
while (x != j);

順列は、異なる要素の1つ以上のサブセットSで構成されているためです。、s1、... sk-1 ここでj = a [sj-1] 1からk-1までのすべてのj、およびs = a [sk-1]、したがって、すべての要素がサイクルに関与します-重複の1つはそのようなサブセットの一部ではありません。

例えば配列= [2、1、4、6、8、7、9、3、8]の場合

その場合、他のすべての要素がサイクルを形成するため、位置5の太字の要素は重複しています:{2-> 1、4-> 6-> 7-> 9-> 8-> 3}。一方、配列[2、1、4、6、5、7、9、3、8]および[2、1、4、6、3、7、9、5、8]は有効な順列です(サイクル{2 -> 1、4-> 6-> 7-> 9-> 8-> 3、5}および{2-> 1、4-> 6-> 7-> 9-> 8-> 5-> 3}それぞれ)。

アブダリは重複を見つける方法に入ります。基本的に、次のアルゴリズム( フロイドの循環検出アルゴリズム を使用)は、問題の重複の1つに遭遇した場合に機能します。

function is_duplicate(a, N, j)
{
     /* assume we've already scanned the array to make sure all elements
        are integers between 1 and N */
     x1 := j;
     x2 := j;
     do
     {             
         x1 := a[x1];
         x2 := a[x2];
         x2 := a[x2];
     } while (x1 != x2);

     /* stops when it finds a cycle; x2 has gone around it twice, 
        x1 has gone around it once.
        If j is part of that cycle, both will be equal to j. */
     return (x1 != j);
}

難しさは、述べられているあなたの問題が彼の論文の問題と一致するかどうかわからないことです。また、彼が説明するメソッドがO(N)で実行されるか、固定を使用するかどうかもわかりません。スペースの量。潜在的な反例は次の配列です。

[3、4、5、6、7、8、9、10、... N-10、N-9、N-8、N-7、N-2、N-5、N-5、N- 3、N-5、N-1、N、1、2]

これは基本的に2だけシフトされた単位順列であり、要素[N-6、N-4、およびN-2]は[N-2、N-5、N-5]に置き換えられます。これは正しい合計です(正しい積ではありませんが、任意精度の算術でN!を計算するためのスペース要件はO(N)であり、違反するため、可能な検出方法として積を取ることを拒否します「固定メモリスペース」要件の精神)、そしてサイクルを見つけようとすると、サイクル{3-> 5-> 7-> 9-> ... N-7-> N-5-> N-1-}および{4-> 6-> 8-> ... N-10-> N-8-> N-2-> N-> 2}。問題は、最大Nサイクルになる可能性があることです。 、(ID順列にはNサイクルがあります)それぞれが重複を見つけるためにO(N))を要し、どのサイクルがトレースされ、どのサイクルがトレースされていないかを追跡する必要があります。一定のスペースでこれを行うことが可能であることに懐疑的ですが、おそらくそうです。

これは十分に重い問題なので、 mathoverflow.net で質問する価値があります(ほとんどの場合、mathoverflow.netはstackoverflowで引用されていますが、簡単すぎる問題のためです)


編集:私は mathoverflowについて質問 、そこにはいくつかの興味深い議論があります。

16
Jason S

これは、少なくともシングルスキャンアルゴリズムでは、O(1)スペースで行うことは不可能です。

証明

N個の要素のうちN/2個を処理したとします。シーケンスが順列であると仮定すると、アルゴリズムの状態が与えられると、残りのN/2個の要素のセットを把握できるはずです。残りの要素がわからない場合は、古い要素のいくつかを繰り返すことでアルゴリズムをだますことができます。

N個の選択N/2個の可能な残りのセットがあります。それらのそれぞれは、アルゴリズムの個別の内部状態で表す必要があります。そうしないと、残りの要素を理解できなかったためです。ただし、X状態を格納するには対数空間が必要であるため、N選択N/2状態を格納するにはBigTheta(log(N Choose N/2))空間が必要です。その値はNとともに大きくなるため、アルゴリズムの内部状態はO(1)スペースに収まりません)。

より正式な証明

最終的なN/2要素とN/2要素を処理した後の線形時定数空間アルゴリズムの内部状態を前提として、シーケンス全体が1の順列であるかどうかを判別するプログラムPを作成するとします。 .N。この二次プログラムには時間やスペースがありません。

Pが存在すると仮定すると、線形時定数空間アルゴリズムの内部状態のみを取得してプログラムQを作成できます。これにより、シーケンスの必要な最終N/2要素が決定されます(順列の場合)。 Qは、可能なすべての最終N/2要素をPに渡し、Pがtrueを返すセットを返すことによって機能します。

ただし、QにはN/2の可能な出力を選択するNがあるため、少なくともNのN/2の可能な入力を選択する必要があります。つまり、元のアルゴリズムの内部状態には、少なくともN個の選択N/2状態が格納されている必要があり、一定サイズよりも大きいBigTheta(logN選択N/2)が必要です。

したがって、時間と空間の境界がある元のアルゴリズムも、一定サイズの内部状態がある場合は正しく機能しません。

[このアイデアは一般化できると思いますが、思考は証明されていません。]

結果

BigTheta(log(N Choose N/2))はBigTheta(N)と同じです。したがって、ブール配列を使用し、値に遭遇したときに値をチェックするだけで、(おそらく)スペースが最適になり、線形時間がかかるため、時間も最適になります。

10
Craig Gidney

私はあなたがそれを証明できるとは思えません;)

  (1, 2, 4, 4, 4, 5, 7, 9, 9)

より一般的には、この問題は番号を順番に処理することでは解決できないと思います。要素を順番に処理していて、配列の途中にいるとします。これで、プログラムの状態は、これまでに遭遇した数値を何らかの形で反映する必要があります。これには、少なくともO(n)ビットを格納する必要があります。

5
Jules

複雑さがMではなくNの関数として与えられているため、これは機能しません。これは、N >> Mであることを意味します。

これは私のショットでしたが、ブルームフィルターを使用するには、大きなMが必要です。その時点で、整数などの単純なビット切り替えを使用することもできます。

http://en.wikipedia.org/wiki/Bloom_filter

配列内の要素ごとにkハッシュ関数を実行します。ブルームフィルターに含まれているかどうかを確認します。含まれている場合は、以前に要素を見たことがある可能性があります。含まれていない場合は、追加します。

完了したら、1..N配列の結果と順番に比較することもできます。これは、別のNしかかからないためです。

ここで、十分な警告を入力しなかった場合。Nで複雑さを指定したため、100%でも、近いものでもありません。これは、N >> Mを意味するため、基本的に、指定したとおりに機能しません。

ところで、個々のアイテムの偽陽性率はe = 2 ^(-m /(n * sqrt(2)))である必要があります

どのサルが一緒にいるのかによって、Mがどれだけ大きくなる必要があるかがわかります。

3
McBeth

サイズO(n)のランダムに選択されたさまざまな定数Cを法としてsum(x_i)product(x_i)を計算することにより、ランダム化されたO(n)時間と定数空間でこれを実行できる場合があります。これにより、基本的にproduct(x_i)が大きくなりすぎるという問題を回避できます。

ただし、sum(x_i)=N(N+1)/2product(x_i)=N!が順列を保証するのに十分な条件であるかどうか、および非順列が誤検出を生成する可能性はどのくらいかなど、まだ多くの未解決の質問があります(私は願っています〜試行するCごとに1/Cですが、そうではない可能性があります)。

1
Keith Randall

O(N)でそれを行う方法がわからない、またはO(N)でそれを行うことができるとしても。 (適切な)ソートと比較を行うと、O(N log N)で実行できることを私は知っています。

そうは言っても、一方が他方の順列ではないことを示すために実行できる多くのO(N)手法があります。

  1. 長さを確認してください。等しくない場合、明らかに順列ではありません。
  2. XORフィンガープリントを作成します。XORされたすべての要素の値が一致しない場合、それを順列にすることはできません。ただし、一致は決定的ではありません。
  3. すべての要素の合計を求めます。結果がオーバーフローする可能性がありますが、この「指紋」を一致させる場合は心配する必要はありません。ただし、乗算を伴うチェックサムを実行した場合は、オーバーフローが問題になります。

お役に立てれば。

1
Sparky
int solution(int A[], int N) {
  int i,j,count=0, d=0, temp=0,max;
  for(i=0;i<N-1;i++) {
    for(j=0;j<N-i-1;j++) {
      if(A[j]>A[j+1]) {
        temp = A[j+1];
        A[j+1] = A[j];
        A[j] = temp;
      }
    }
  }
  max = A[N-1];
  for(i=N-1;i>=0;i--) {
    if(A[i]==max) {
      count++;
    }
    else {
      d++;
    }
    max = max-1;
  }
  if(d!=0) {
    return 0;
  }
  else {
    return 1;
  }
}
0
shubh_

スタックマシンで配列内の重複を見つけるように求めているように見えます。

スタックの完全な履歴を知ることは不可能に聞こえますが、各番号を抽出し、取り出された番号についての知識は限られています。

0
none

了解しました。これは異なりますが、機能しているようです。

このテストプログラム(C#)を実行しました:

    static void Main(string[] args) {
        for (int j = 3; j < 100; j++) {
            int x = 0;
            for (int i = 1; i <= j; i++) {
                x ^= i;
            }
            Console.WriteLine("j: " + j + "\tx: " + x + "\tj%4: " + (j % 4));
        }
    }

簡単な説明:xは単一のリストのすべてのXORの結果、iは特定のリストの要素、jはリストのサイズです。私がしているのはXORだけなので、要素の順序は重要ではありません。しかし、私はこれが適用されたときに正しい順列がどのように見えるかを見ています。

J%4を見ると、その値をオンにして、次のようなものを取得できます。

    bool IsPermutation = false;
    switch (j % 4) {
        case 0:
            IsPermutation = (x == j);
            break;
        case 1:
            IsPermutation = (x == 1);
            break;
        case 2:
            IsPermutation = (x == j + 1);
            break;
        case 3:
            IsPermutation = (x == 0);
            break;
    }

これにはおそらく微調整が必​​要であることを認めます。 100%ではありませんが、簡単に始めることができます。たぶん、XORループ全体でいくつかの小さなチェックが実行されていれば、これは完璧かもしれません。そのあたりから始めてみてください。

0
Corey Ogburn

次の解決策を確認してください。 O(1) additionalスペースを使用します。チェックプロセス中に配列を変更しますが、最後に初期状態に戻します。

アイデアは次のとおりです。

  1. 要素のいずれかが[1、n] => O(n)の範囲外にあるかどうかを確認します。
  2. 番号を順番に調べ(すべてが[1、n]の範囲にあることが保証されます)、各番号x(例:3)について:

    • x番目のセル(例:a [3])に移動します。負の場合は、誰かがあなたの前にすでにそのセルにアクセスしています=>順列ではありません。それ以外の場合(a [3]は正)、-1を掛けます。 => O(n)。
  3. 配列に目を通し、すべての負の数を否定します。

このようにして、すべての要素が[1、n]の範囲にあり、重複がないことが確実にわかります=>配列は順列です。

int is_permutation_linear(int a[], int n) {
    int i, is_permutation = 1;

    // Step 1.
    for (i = 0; i < n; ++i) {
        if (a[i] < 1 || a[i] > n) {
            return 0;
        }
    }

    // Step 2.
    for (i = 0; i < n; ++i) {
        if (a[abs(a[i]) - 1] < 0) {
            is_permutation = 0;
            break;
        }
        a[i] *= -1;
    }

    // Step 3.
    for (i = 0; i < n; ++i) {
        if (a[i] < 0) {
            a[i] *= -1;
        }
    }

    return is_permutation;
}

これをテストする完全なプログラムは次のとおりです。

/*
 * is_permutation_linear.c
 *
 *  Created on: Dec 27, 2011
 *      Author: Anis
 */

#include <stdio.h>

int abs(int x) {
    return x >= 0 ? x : -x;
}

int is_permutation_linear(int a[], int n) {
    int i, is_permutation = 1;

    for (i = 0; i < n; ++i) {
        if (a[i] < 1 || a[i] > n) {
            return 0;
        }
    }

    for (i = 0; i < n; ++i) {
        if (a[abs(a[i]) - 1] < 0) {
            is_permutation = 0;
            break;
        }
        a[abs(a[i]) - 1] *= -1;
    }

    for (i = 0; i < n; ++i) {
        if (a[i] < 0) {
            a[i] *= -1;
        }
    }

    return is_permutation;
}

void print_array(int a[], int n) {
    int i;
    for (i = 0; i < n; i++) {
        printf("%2d ", a[i]);
    }
}

int main() {
    int arrays[9][8] = { { 1, 2, 3, 4, 5, 6, 7, 8 },
                         { 8, 6, 7, 2, 5, 4, 1, 3 },
                         { 0, 1, 2, 3, 4, 5, 6, 7 },
                         { 1, 1, 2, 3, 4, 5, 6, 7 },
                         { 8, 7, 6, 5, 4, 3, 2, 1 },
                         { 3, 5, 1, 6, 8, 4, 7, 2 },
                         { 8, 3, 2, 1, 4, 5, 6, 7 },
                         { 1, 1, 1, 1, 1, 1, 1, 1 },
                         { 1, 8, 4, 2, 1, 3, 5, 6 } };
    int i;

    for (i = 0; i < 9; i++) {
        printf("array: ");
        print_array(arrays[i], 8);
        printf("is %spermutation.\n",
               is_permutation_linear(arrays[i], 8) ? "" : "not ");
        printf("after: ");
        print_array(arrays[i], 8);
        printf("\n\n");

    }

    return 0;
}

そしてその出力:

array:  1  2  3  4  5  6  7  8 is permutation.
after:  1  2  3  4  5  6  7  8 

array:  8  6  7  2  5  4  1  3 is permutation.
after:  8  6  7  2  5  4  1  3 

array:  0  1  2  3  4  5  6  7 is not permutation.
after:  0  1  2  3  4  5  6  7 

array:  1  1  2  3  4  5  6  7 is not permutation.
after:  1  1  2  3  4  5  6  7 

array:  8  7  6  5  4  3  2  1 is permutation.
after:  8  7  6  5  4  3  2  1 

array:  3  5  1  6  8  4  7  2 is permutation.
after:  3  5  1  6  8  4  7  2 

array:  8  3  2  1  4  5  6  7 is permutation.
after:  8  3  2  1  4  5  6  7 

array:  1  1  1  1  1  1  1  1 is not permutation.
after:  1  1  1  1  1  1  1  1 

array:  1  8  4  2  1  3  5  6 is not permutation.
after:  1  8  4  2  1  3  5  6 
0
Anis Abboud

以下のJavaソリューションは、質問に部分的に答えます。時間計算量はO(n)だと思います。 (この信念は、ソリューションにネストされたループが含まれていないという事実に基づいています。)メモリについて-わからない。質問はグーグルの関連するリクエストで最初に表示されるので、おそらく誰かに役立つ可能性があります。

public static boolean isPermutation(int[] array) {   
    boolean result = true;
    array = removeDuplicates(array);
    int startValue = 1;
    for (int i = 0; i < array.length; i++) {
        if (startValue + i  != array[i]){
            return false;
        }
    }
    return result;
}
public static int[] removeDuplicates(int[] input){
    Arrays.sort(input);
    List<Integer> result = new ArrayList<Integer>();
    int current = input[0];
    boolean found = false;

    for (int i = 0; i < input.length; i++) {
        if (current == input[i] && !found) {
            found = true;
        } else if (current != input[i]) {
            result.add(current);
            current = input[i];
            found = false;
        }
    }
    result.add(current);
    int[] array = new int[result.size()];
    for (int i = 0; i < array.length ; i ++){
        array[i] = result.get(i);
    }
    return array;
}
public static void main (String ... args){
    int[] input = new int[] { 4,2,3,4,1};
    System.out.println(isPermutation(input));
    //output true
    input = new int[] { 4,2,4,1};
    System.out.println(isPermutation(input));
    //output false
}
0
yurin

Nに対するスペースの量に応じて、ハッシュとバケットを使用してみてください。

つまり、リスト全体を反復処理し、各要素をハッシュして、バケットに格納します。ハッシュからのバケットの衝突を減らす方法を見つける必要がありますが、それは解決された問題です。

要素がそれと同一のアイテムでバケットに入ろうとする場合、それは順列です。

このタイプのソリューションは、各要素に1回だけ触れると、O(N)になります。

ただし、これに伴う問題は、スペースMがNより大きいかどうかです。 M> Nの場合、このソリューションは問題ありませんが、M <Nの場合、100%の精度で問題を解決することはできません。

0
samoz

配列に重複する値がない場合にのみ、O(N)で簡単に確認できるはずです。

0
Chris Card

これらのハッシュは衝突する可能性があるため、合計と積は正しい答えを保証しません。つまり、入力が異なると同じ結果が得られる可能性があります。完全なハッシュ、つまり配列の数値構成を実際に完全に説明する単一の数値の結果が必要な場合は、次のようになります。

_[1, N]_の範囲内の任意の数iに対して、一意の素数P(i)を生成できると想像してください(たとえば、P(i)はi番目の素数です)。これで、配列内のすべての数値について、すべてのP(i)の積を計算するだけで済みます。この製品は、配列内の値の順序を無視して、配列の構成を完全かつ明確に記述します。あなたがする必要があるのは、(順列の)「完全な」値を事前に計算し、それを与えられた入力の結果と比較することです:)

もちろん、このようなアルゴリズムは、投稿された要件をすぐには満たしません。しかし同時に、それは直感的にあまりにも一般的です。配列内の絶対的なany数値の組み合わせの順列を検出することができます。あなたの場合、特定の組み合わせ_1, 2, ..., N_の順列を検出する必要があります。多分これは物事を単純化するためにどういうわけか使用することができます...おそらくそうではありません。

0
AnT

まず、これが可能であるかもしれない情報理論的理由。配列内の数値がO(N)時間とO(1)空間で範囲内にあることを簡単に確認できます。このような配列を指定するにはインバウンド数にはN log Nビットの情報が必要ですが、並べ替えを指定するには、約(N log N) - Nビットの情報が必要です(スターリングの近似)。したがって、中にNビットの情報を取得できる場合テストすると、答えを知ることができるかもしれません。これはN時間で行うのは簡単です(実際、M静的空間を使用すると、ステップごとにlog M情報を非常に簡単に取得できます。 、および特別な状況下では、log N情報を取得できます)。

一方、静的ストレージスペースにはM log Nビットのような情報しか格納できません。これは、おそらくNよりはるかに小さいため、決定面の形状に大きく依存します。 「順列」と「ない」の間にあります。

これはほぼ可能だと思いますが、問題の設定を完全に考慮しているわけではありません。 (Iulianが言及したリンクのように)サイクリングトリックを使用することが「想定」されていると思いますが、ここでは、can配列の最後の要素に順列のインデックスを付けます。

0
Rex Kerr

これが証明できません:

いくつかの工夫により、最後のセル以外のすべてで重複が検出されなかったとします。次に、問題は、最後のセルに重複が含まれているかどうかを確認することになります。

これまでのところ、問題の状態のno構造化表現がある場合は、各セルについて、前の入力全体に対して線形検索を実行することになります。これにより、2次時間アルゴリズムがどのように得られるかを簡単に確認できます。

ここで、いくつかの巧妙なデータ構造を通じて、最後に表示されると予想される番号を実際に知っていると仮定します。それなら確かに、その知識はあなたが探している数を保存するのに少なくとも十分なビットを必要とします-おそらく1つのメモリセル?しかし、最後から2番目の数と最後から2番目のサブ問題があります。その場合、まだ見られていない2つの可能な数のセットを同様に表す必要があります。これは確かに、残りの1つの番号だけをエンコードするよりも多くのストレージを必要とします。二次時間の最悪の場合を受け入れる意思がない限り、同様の議論が進むにつれて、状態のサイズは問題のサイズとともに大きくなる必要があります。

これは時空間のトレードオフです。二次時間と一定の空間、または線形時間と線形空間を持つことができます。線形時間と一定の空間を持つことはできません。

0
Ian