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


88

最近私はインタビューをしていて、彼らは私に「検索」の質問をしました。
問題は:

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

例:

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

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

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

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

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

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

正しい答えは何ですか?


4
私の知る限り、線形検索は配列内の要素のインデックスを見つけるための良い方法です。要素のインデックスを見つけるのに効率的な別の検索アルゴリズムについてはまだわかりません。
Sean Francis N. Ballais、2015

4
7が1回だけ出現することが保証されている場合、またはどの7が返されるかが問題ではない場合は、コールマンの回答の線形アルゴリズムをさらに改善できます。
user1942027

52
元のソリューションで並べ替えが必要な場合は、単純な線形検索よりも悪くなります。あなたはそれに気づいていないようです。
cubuspl42

5
ソートにはO(nlogn)が必要であり、バイナリ検索はO(logn)です。大規模な配列から多数の値を検索する必要がある場合は、答えが良いかもしれませんが、一度だけ検索する場合は、O(n)アルゴリズムの方が良いかもしれません。
jingyu9575

23
なぜ他の誰もこれについて言及しなかったのかはわかりません。あなたの方法は非効率的であっただけでなく、不正確でした、そしてそれは単なる非効率よりもはるかに悪いです。要件は、元の配列内の指定された数値の位置です。メソッドは、ソートされた配列内の数値の位置を返します。これで、並べ替えの前に単純な配列をタプルの配列(数値、orig_pos)に変換することで、元の位置を取得できます。しかし、あなたはそれについて言及しなかったので、私もあなたがインタビューでそれについて言及しなかったと思います。
トムZych

回答:


126

1より大きいことが多いステップで線形検索を実行できます。重要な観察は、たとえばarray[i] == 47がまだ表示されていない場合、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

8
まさに私が考えていたもの。これはですO(N)が、もっと速い方法はないと思います。
shapiro yaacov

2
より多くの候補(最初と最後など)を使用して平均で少し速く、次にターゲットに最も近い候補を使用することができます。つまり、最初の出現ではなく単一の出現のみを検索する必要がある場合です。
mkadunc 2015

2
@mkaduncそれは良い考えです。別の観察では、最初と最後の要素が7にまたがる場合、その特別な場合にバイナリ検索を使用できます(どの7が検出されても構わない場合)
John Coleman

1
7を見つける必要がある場合(必ずしも最初のものではない)、次の(実用的な)改善を提案します。セクションのリスト(「start」と「end」の2つの整数)を作成し、配列の先頭から開始するのではなく、中央から開始します。セルの値に応じて、関連する範囲を無視し、残りの2つのセクションをセクションのリストに追加します。リストの次のアイテムについて繰り返します。これはまだ「O(n)」ですが、セルをチェックするたびに2倍の範囲を無視します。
shapiro yaacov

3
@ShapiroYaacov:セクションの両側の値の低い方から高い方への間隔にk(7)が含まれているかどうかのチェックと組み合わせると、これは独自の答えに値します。
greybeard

35

あなたのアプローチは複雑すぎます。すべての配列要素を調べる必要はありません。最初の値がされ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からのコメントの後に改善されました。


2
ちょっとしたif ((skip = 7 - array[i]) < 1) skip = 1;ことは、それを複雑にしすぎて、私の意見ではそれを悲観的にするようです。場合はarray[i] == 200、あなたが取得-193し、あなたがなぜだけではなく、すべての193をスキップすることができていてもちょうど1で毎回スキップi += abs(7 - array[i])
user1942027 2015

1
skip7との絶対差に設定する必要がありarray[i]ます。
Martin Zabel、2015

@Raphael Miedlいいえ、要素はありません200、あなたは合格したでしょう7
Weather Vane

3
@WeatherVane保証はありません。隣接する値が相互に+1/ -1相互に関連しているだけです。だからそれはちょうどでarray[0] == 200あり、他はほとんど-1のです。
user1942027 2015

1
@WeatherVaneこれは、要素が常に配列にあるという前提に基づいていますが、そうではない場合もあります。その場合、-1は有効な戻り値です。これにより、コードがかなり変わります
Eugene

20

従来の線形検索のバリエーションが良い方法かもしれません。と言う要素を選びましょう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次にインデックスをチェックする必要があり、whileループを使用して、インデックスで見つかった値に応じて、チェックする次のインデックスを決定できますi + 5

これには複雑さO(n)(漸近的な複雑さの点では線形時間)がありますが、すべてのインデックスがアクセスされないため、実際の点では通常の線形検索よりも優れています。

array[i](私たちの出発点)が奇数だった場合、明らかにこれらすべてが逆になります。


8

ジョンコールマンが提示したアプローチは、おそらく面接担当者が望んでいたものです。
かなり複雑に進んでも構わない場合は、予想スキップ長を長くすることができます。
ターゲット値kを呼び出します。位置pにある最初の要素の値vから始め、差kv dvを絶対値avで呼び出します。否定的な検索を高速化するには、最後の要素を他の値uとして位置を確認します oに:dv×duが負の場合、kが存在します(kの発生が許容できる場合は、バイナリ検索のようにここでインデックス範囲を狭めることができます)。av + auが配列の長さより大きい場合、kは存在しません。(dv×duがゼロの場合、vまたはuはkに等しくなります。)
インデックスの有効性の省略:シーケンスが途中でkでvに戻る(「次の」)位置をプローブしますo = p + 2*av
dv×duが負の場合、p + avからo-auまでのk(再帰的に?)を求めます。
ゼロの場合、uはoでkに等しくなります。
duがdvに等しく、中央の値がkでない場合、またはauがavを超え
ている場合、またはp + avからo-auへのkを見つけることができない場合は
letp=o; dv=du; av=au;プロービング続けます。
(60年代のテキストを完全にフラッシュバックするには、Courierで表示します。私の「1st 2nd Thought」は、o = p + 2*av - 1、これはduがdvに等しいことを排除します。)


3

ステップ1

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

ステップ2

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

ステップ3

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

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


これはJohn Colemanの回答とどう違うのですか?(|c-7|どこが必要|array[c]-7|と思われるかを示唆することは 別として。)
greybeard

私は彼の答えを見ました。基本的な考え方は同じです。
Akeshwar Jha

元の質問は、配列が7未満の数字で始まることを規定していません。したがってarray[c]-7、正または負の可能性があります。abs()前にスキップする前に、それに適用する必要があります。
arielf 2015

はい、あなたが正しい。それarray[c] - 7が、モジュラス演算子を使用している理由です|array[c] - 7|
Akeshwar Jha

3

ここで私は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;
}

2
ドキュメント化されていない、少なくとも準公式の規約を持つ言語のコード。これは、タグ「c」を自由に解釈することを除いて、John ColemanおよびAkeshwarの回答とどのように異なるのですか?
greybeard

3

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

#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;                                                                     

}

neal-fultz答えは最初の出現ではなく、検索要素のランダムな出現を返します。途中から開始して両側からスキップします。
Ram Patra

再帰の順序の切り替えは、読者への課題として残されています。
Neal Fultz、2015

1
neal-fultz次に、printf()メソッド呼び出しでメッセージを編集してください。
Ram Patra、2015

1

const findMeAnElementsFunkyArray = (arr, ele, i) => {
  const elementAtCurrentIndex = arr[i];

  const differenceBetweenEleAndEleAtIndex = Math.abs(
    ele - elementAtCurrentIndex
  );

  const hop = i + differenceBetweenEleAndEleAtIndex;

  if (i >= arr.length) {
    return;
  }
  if (arr[i] === ele) {
    return i;
  }

  const result = findMeAnElementsFunkyArray(arr, ele, hop);

  return result;
};

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

const answer = findMeAnElementsFunkyArray(array, 7, 0);

console.log(answer);

問題への再帰的な解決策を含めたかった。楽しい

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.