web-dev-qa-db-ja.com

インタビューの質問-ソートされた配列XでX [i] = iとなるようなインデックスiを検索します

私は昨日のインタビューで次の質問をされました:

JavaまたはC++配列はXとソートされ、2つの要素が同じではないことを考慮してください。インデックスを見つけるには、次のようなiそのインデックスの要素もiです。つまり、X[i] = i

説明として、彼女は私にも例を挙げました:

Array X : -3 -1 0 3 5 7
index   :  0  1 2 3 4 5

Answer is 3 as X[3] = 3.

私が考えることができた最高のものは、線形探索でした。インタビューの後、私はこの問題について多くのことをしましたが、より良い解決策を見つけることができませんでした。私の引数は:必要なプロパティを持つ要素は、配列のどこにでも置くことができます。したがって、配列の最後にある可能性もあるので、すべての要素をチェックする必要があります。

私はここのコミュニティから私が正しいことを確認したかっただけです。私が正しいと教えてください:)

50
John

これは、わずかに変更された 二分探索 を使用することにより、O(logN)時間およびO(1)空間で実行できます。

_Y[i] = X[i] - i_であるような新しい配列Yを考えます

_Array X : -3 -1   0  3  5  7
index   :  0  1   2  3  4  5
Array Y : -3 -2  -2  0  1  2
_

Xの要素は増加の順序であるため、新しい配列Yの要素はnonになります-減少順序。したがって、Yの_0_のバイナリ検索で答えが得られます。

ただし、Yの作成にはO(N)スペースとO(N)時間かかります。新しい配列を作成する代わりに、_Y[i]_への参照が_X[i] - i_に置き換えられるようにバイナリ検索を変更するだけです。

アルゴリズム:

_function (array X) 
       low  = 0
       high = (num of elements in X) - 1

       while(low <= high) 
               mid = (low + high) / 2

               // change X[mid] to X[mid] - mid
               if(X[mid] - mid == 0)
                       return mid

               // change here too
               else if(X[mid] - mid < 0)
                       low = mid + 1;

               else
                       high = mid - 1;
       end while

       return -1 // no such index exists...return an invalid index.

end function
_

Java実装

C++実装

104
codaddict

O(n)の代わりにO(log n)または場合によってはO(log log n)を平均するいくつかのより高速なソリューションがあります。 "binary search"および"interpolation search"のグーグルを作成すると、非常に良い説明が見つかるでしょう。

配列が並べ替えられていない場合、はい、要素はどこにでもあり、O(n)を下回ることはできませんが、並べ替えられた配列には当てはまりません。

-

要求された補間検索に関するいくつかの説明:

二分探索は「より大きい/大きくない」という点で2つの要素を比較することだけに関係しますが、補間検索は数値も利用しようとします。ポイントは次のとおりです。0から20000までの値の並べ替えられた範囲があります。300を探します。バイナリ検索は範囲の半分の10000から始まります。補間検索では、300はおそらく0に近いと推測されます。 20000よりも大きいため、10000ではなく、最初に要素6000をチェックします。次に、再び-それが高すぎる場合は、下位のサブ範囲に再帰し、低すぎる場合は、上位のサブ範囲に再帰します。

+-値の均一な分布を持つ大きな配列の場合、補間検索はバイナリ検索よりもはるかに高速に動作するはずです-コーディングして実際に確認してください。 また、最初に1つの補間検索ステップを使用し、次に1つのバイナリ検索ステップを使用する場合などに最も効果的です。

辞書で何かを検索するときに、人間が直感的に行うことに注意してください。

9
Kos

@codaddictの answer で提案されているように、配列Yについて考える必要はありません。

バイナリ検索を使用して、指定された配列の中央の要素がインデックスよりも小さい場合は、配列がソートされているため、インデックスが小さいかどうかを確認する必要がないので、左に移動してm個のインデックスを減算して(少なくとも)m値。後続のすべての要素も小さすぎます。例えば。 if _arr[5] = 4_ then arr[4] <= (4 - 1) and arr[3] <= (4 - 2)など。中間の要素がそのインデックスより大きい場合、同様のロジックを適用できます。

シンプルなJavaの実装は次のとおりです。

_int function(int[] arr) {
        int low = 0;
        int high = arr.length - 1;

        while(low <= high) {
            int mid = high - (high - low) / 2;

            if(arr[mid] == mid) {
                 return mid;
            } else if(arr[mid] < mid) {
                 low = mid + 1;
            } else {
                 high = mid - 1;
            }
        }

        return -1; // There is no such index
}
_

上記のソリューションは、すべての要素が異なる場合にのみ機能することに注意してください。

8
Vasu

これはもっと速いと思います。

リストの真ん中から始める

X [i]> iの場合、残りの左側の中央に移動します

x [i] <iの場合、残りの右の中央に移動します

それを続けると、ループごとに可能な要素の数が半分になります

7
JOTN

バイナリ検索を実行できます。値がインデックスよりも小さい場合は、真ん中を検索して、同じ値が含まれるインデックスが下にないようにします。

次に、上半分を検索し、要素が見つかるまで続行するか、1つの要素スパンに到達します。

3
Mor Shemesh

これは私が思いついた解決策であり、重複がある場合に機能します(重複がないという警告を誤って見落としました)。

//invariant: startIndex <= i <= endIndex

int modifiedBsearch(int startIndex, int endIndex)
{
   int sameValueIndex = -1;
   int middleIndex = (startIndex + endIndex) /2;
   int middleValue = array[middleIndex];
   int endValue = array[endIndex];
   int startValue = array[startIndex];

   if(middleIndex == middleValue)
      return middleValue;
   else {
      if(middleValue <= endIndex)
         sameValueIndex = modifiedBsearch(middleIndex + 1, endIndex)

      if(sameValueIndex == -1 && startValue <= middleIndex)
         sameValueIndex = modifiedBsearch(startIndex, middleIndex -1);
   }

   return sameValueIndex;

}

これにはO(log n)の時間がかかると思いますが、一見するとわかりません???

運が悪いと、O(n log n)時間かかります(スタックツリーの高さはlog nになり、最後のレベルにn個のノードがあり、次はn/2個のフルツリーになります)持続するなど)。

したがって、平均するとO(log n)とO(n log n)の間になります。

1
Henley Chiu

Java:

public static boolean check (int [] array, int i)
{
    if (i < 0 || i >= array.length)
        return false;

    return (array[i] == i);
}

C++:

bool check (int array[], int array_size, int i)
{
    if (i < 0 || i >= array_size)
        return false;

    return (array[i] == i);
}
0
Khaled.K

私の頭の上では、バイナリ分割を行う方が高速かもしれません。

真ん中の値を見て、それが必要な値より高い場合は、下半分で再検索します。

1つの比較の後、データセットはすでに半分になっています

0
thecoshman

質問を読んだ後、検索を高速化するために使用できるシナリオが1つあるようです。位置と値を比較するとき、値が位置より大きい場合、その値を評価する次の位置として使用できます。これにより、アレイをすばやくジャンプできます。配列がソートされているため、これを行うことができます。スキップしている値は、概念的には配列内で左にシフトされ、間違った場所にあります。

例:

int ABC[] = { -2, -5, 4, 7, 11, 22, 55 };

現在の位置が2で値が4の場合、それらは等しくなく、概念的には値4が左にシフトされます。次の位置として4の値を使用できます。値4が位置がずれている場合、4未満のすべても位置がずれているためです。

議論のためのいくつかのサンプルコード:

void main()
{
    int X[] = { -3, -1, 0, 3, 5, 7};
    int length = sizeof(X)/sizeof(X[0]);

    for (int i = 0; i < length;) {
        if (X[i] > i && X[i] < length)
            i = X[i];                 // Jump forward!
        else if (X[i] == i) {
            printf("found it %i", i);
            break;
        } else
            ++i;
    }
}
0
skimobear

バイナリ検索の修正バージョンで十分だと思います

シーケンスが

Array : -1 1 4 5 6
Index :  0 1 2 3 4

Result : 1

または

Array : -2 0 1 2 4 6 10
Index :  0 1 2 3 4 5 6 

Result: 4

両方の例から、mid <a [mid] ...疑似コードが次のようになる場合、必要な結果が右側にないことがわかります。

mid <- (first + last )/2

if a[mid] == mid then
       return mid

else if a[mid] < mid then
        recursive call (a,mid+1,last)

else 
         recursive call (a,first,mid-1)
0
NirmalGeo