web-dev-qa-db-ja.com

要素を検索する効率的な方法

最近、私はインタビューを受けました。彼らは私に「searching」という質問をしました。
質問は:

(正の)整数の配列があり、その各要素は、隣接する要素と比較して+1または-1のいずれかであると仮定します。

例:

array = [4,5,6,5,4,3,2,3,4,5,6,7,8];

7を検索して、その位置を返します。

私はこの答えをしました:

値を一時配列に格納し、並べ替えてから、バイナリ検索を適用します。

要素が見つかった場合、一時配列内の位置を返します。
(番号が2回発生している場合、最初の発生を返します)

しかし、彼らはこの答えに満足していないようでした。

正しい答えは何ですか?

87
NSUser

多くの場合、1より大きいステップで線形検索を実行できます。重要な観察結果は、 array[i] == 4と7がまだ出現していない場合、7の次の候補はインデックスi+3。次の実行可能な候補に繰り返し直接移動するwhileループを使用します。

以下に、少し一般化した実装を示します。配列内で最初に現れるk(+ = 1の制限の対象)または-1発生しない場合:

#include <stdio.h>
#include <stdlib.h>

int first_occurence(int k, int array[], int n);

int main(void){
    int a[] = {4,3,2,3,2,3,4,5,4,5,6,7,8,7,8};
    printf("7 first occurs at index %d\n",first_occurence(7,a,15));
    printf("but 9 first \"occurs\" at index %d\n",first_occurence(9,a,15));
    return 0;
}

int first_occurence(int k, int array[], int n){
    int i = 0;
    while(i < n){
        if(array[i] == k) return i;
        i += abs(k-array[i]);
    }
    return -1;
}

出力:

7 first occurs at index 11
but 9 first "occurs" at index -1
124
John Coleman

あなたのアプローチは複雑すぎます。すべての配列要素を調べる必要はありません。最初の値は4、 そう 7少なくとも7-4個の要素があり、それらはスキップできます。

#include <stdio.h>
#include <stdlib.h>

int main (void)
{
    int array[] = {4,5,6,5,4,3,2,3,4,5,6,7,8};
    int len = sizeof array / sizeof array[0];
    int i = 0;
    int steps = 0;
    while (i < len && array[i] != 7) {
        i += abs(7 - array[i]);
        steps++;
    }

    printf("Steps %d, index %d\n", steps, i);
    return 0;
}

プログラム出力:

Steps 4, index 11

編集:@Raphael Miedlと@Martin Zabelからのコメントの後に改善。

34
Weather Vane

従来の線形検索のバリエーションは良い方法です。 _array[i] = 2_という要素を選択してみましょう。現在、_array[i + 1]_は1または3(奇数)、_array[i + 2]_は(正の整数のみ)2または4(偶数)のいずれかになります。

このように続けると、パターンが観察可能になります-_array[i + 2*n]_は偶数を保持するため、これらのインデックスはすべて無視できます。

また、私たちはそれを見ることができます

_array[i + 3] = 1 or 3 or 5
array[i + 5] = 1 or 3 or 5 or 7
_

そのため、インデックス_i + 5_を次にチェックする必要があり、インデックス_i + 5_で見つかった値に応じて、whileループを使用して、チェックする次のインデックスを決定できます。

これは複雑さO(n)(漸近的な複雑さの点で線形時間)を持ちますが、すべてのインデックスが訪問されないため、実用的な用語での通常の線形検索よりも優れています。

明らかに、_array[i]_(開始点)が奇数だった場合、これはすべて逆になります。

20
Madhav Datt

ジョン・コールマンによって提示されたアプローチは、インタビュアーが期待していたものであり、おそらくそうです。
かなり複雑になりたい場合は、予想されるスキップの長さを増やすことができます。
ターゲット値を呼び出しますk。最初の要素の値vの位置pで開始し、差k-v dvを絶対値avで呼び出します。ネガティブ検索を高速化するには、他の値として最後の要素を覗く位置o:dv×duが負の場合、kが存在するkは許容範囲です。ここでは、バイナリ検索の方法でインデックス範囲を絞り込むことができます)。 av + auが配列の長さより大きい場合、kはありません。 (dv×duがゼロの場合、vまたはuはkと等しくなります。)
インデックスの有効性を省略する:シーケンスがvに戻る可能性のある(「次の」)位置をプローブします。kは中央にあります:o = p + 2*av
dv×duが負の場合、p + avからo-auまでk(再帰的に?)を見つけます。
ゼロの場合、uはoでkと等しくなります。
duがdvに等しく、中央の値がkでない場合、またはauがavを超える場合、
またはあなたfail p + avからo-auまでのkを見つけるには、
let p=o; dv=du; av=au;そして調査を続けます。
(60年代のテキストへの完全なフラッシュバックについては、Courierで表示します。私の「最初の2番目の考え」はo = p + 2*av - 1duはdvを除外します。)

8
greybeard

ステップ1

最初の要素から始めて、それが7であるかどうかを確認します。cが現在の位置のインデックスであるとします。したがって、最初は、c = 0

ステップ2

7の場合、インデックスが見つかりました。 cです。配列の最後に達した場合は、抜け出します。

ステップ

そうでない場合、7は少なくとも|array[c]-7|は、インデックスごとに1つのユニットしか追加できないため、離れています。したがって、|array[c]-7|現在のインデックスcに移動し、もう一度ステップ2に進んで確認します。

最悪の場合、1と-1が交互に存在する場合、時間の複雑さはO(n)に達する可能性がありますが、平均的なケースは迅速に配信されます。

3
Akeshwar Jha

ここでは、Javaでの実装を提供しています...

public static void main(String[] args) 
{       
    int arr[]={4,5,6,5,4,3,2,3,4,5,6,7,8};
    int pos=searchArray(arr,7);

    if(pos==-1)
        System.out.println("not found");
    else
        System.out.println("position="+pos);            
}

public static int searchArray(int[] array,int value)
{
    int i=0;
    int strtValue=0;
    int pos=-1;

    while(i<array.length)
    {
        strtValue=array[i];

        if(strtValue<value)
        {
            i+=value-strtValue;
        }
        else if (strtValue==value)
        {
            pos=i;
            break;
        }
        else
        {
            i=i+(strtValue-value);
        }       
    }

    return pos;
}
3
kaushik

以下は、分割統治スタイルのソリューションです。 (はるかに)より多くの簿記を犠牲にして、より多くの要素をスキップできます。左から右にスキャンするのではなく、中央でテストし、both方向にスキップします。

#include <stdio.h>                                                               
#include <math.h>                                                                

int could_contain(int k, int left, int right, int width);                        
int find(int k, int array[], int lower, int upper);   

int main(void){                                                                  
    int a[] = {4,3,2,3,2,3,4,5,4,5,6,7,8,7,8};                                   
    printf("7 first occurs at index %d\n",find(7,a,0,14));                       
    printf("but 9 first \"occurs\" at index %d\n",find(9,a,0,14));               
    return 0;                                                                    
}                                                                                

int could_contain(int k, int left, int right, int width){                        
  return (width >= 0) &&                                                         
         (left <= k && k <= right) ||                                            
         (right <= k && k <= left) ||                                            
         (abs(k - left) + abs(k - right) < width);                               
}                                                                                

int find(int k, int array[], int lower, int upper){                              
  //printf("%d\t%d\n", lower, upper);                                            

  if( !could_contain(k, array[lower], array[upper], upper - lower )) return -1;  

  int mid = (upper + lower) / 2;                                                 

  if(array[mid] == k) return mid;                                                

  lower = find(k, array, lower + abs(k - array[lower]), mid - abs(k - array[mid]));
  if(lower >= 0 ) return lower;                                                    

  upper = find(k, array, mid + abs(k - array[mid]), upper - abs(k - array[upper]));
  if(upper >= 0 ) return upper;                                                  

  return -1;                                                                     

}
2
Neal Fultz