Effiziente Möglichkeit, ein Element zu suchen

Lesezeit: 10 Minuten

Benutzeravatar von NSUser
NSBenutzer

Kürzlich hatte ich ein Vorstellungsgespräch, wo sie mich fragten: „suchen” Frage.
Die Frage war:

Angenommen, es gibt ein Array von (positiven) ganzen Zahlen, von denen jedes Element eines von beiden ist +1 oder -1 im Vergleich zu den angrenzenden Elementen.

Beispiel:

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

Jetzt suchen 7 und seine Position zurückgeben.

Ich habe diese Antwort gegeben:

Speichern Sie die Werte in einem temporären Array, sortieren Sie sie und wenden Sie dann die binäre Suche an.

Wenn das Element gefunden wird, geben Sie seine Position im temporären Array zurück.
(Wenn die Zahl zweimal vorkommt, geben Sie ihr erstes Vorkommen zurück)

Aber sie schienen mit dieser Antwort nicht zufrieden zu sein.

Was ist die richtige Antwort?

  • Soweit ich weiß, ist eine lineare Suche eine gute Möglichkeit, den Index eines Elements im Array zu finden. Ich bin mir noch nicht sicher, ob ein anderer Suchalgorithmus den Index eines Elements effizient finden kann.

    – Sean Francis N. Ballais

    27. Dezember 2015 um 14:59 Uhr

  • Wenn 7 garantiert nur einmal erscheint oder es egal ist, welche 7 zurückgegeben wird, können Sie den linearen Algorithmus von Colemans Antwort weiter verbessern.

    – AliciaBytes

    27. Dezember 2015 um 15:12 Uhr

  • Wenn Ihre ursprüngliche Lösung eine Sortierung erfordert, ist dies schlimmer als die naive lineare Suche. Das scheint Ihnen nicht bewusst zu sein.

    – cubuspl42

    27. Dezember 2015 um 22:54 Uhr


  • Das Sortieren erfordert O(nlogn), und eine binäre Suche ist O(logn). Wenn Sie viele viele Werte aus dem großen Array suchen müssen, ist Ihre Antwort möglicherweise besser, aber wenn Sie nur einmal suchen, sind O(n)-Algorithmen möglicherweise besser.

    – jingyu9575

    28. Dezember 2015 um 9:06 Uhr

  • Ich weiß nicht, warum das sonst niemand erwähnt hat: Ihre Methode war nicht nur ineffizient, sie war es auch falsch, und das ist viel schlimmer als bloße Ineffizienz. Die Anforderung ist für die Position einer bestimmten Nummer im ursprünglichen Array. Ihre Methode gibt die Position der Zahl zurück in einem sortierten Array. Jetzt du könnte Rufen Sie die ursprüngliche Position ab, indem Sie das einfache Array vor dem Sortieren in ein Array von Tupeln (number, orig_pos) konvertieren. Aber das hast du nicht erwähnt, also vermute ich, dass du es auch im Interview nicht erwähnt hast.

    – Tom Zych

    29. Dezember 2015 um 9:18 Uhr

Benutzeravatar von John Coleman
John Colemann

Sie können eine lineare Suche mit Schritten durchführen, die oft größer als 1 sind. Die entscheidende Beobachtung ist, dass, wenn z array[i] == 4 und 7 ist noch nicht erschienen, dann ist der nächste Kandidat für 7 im Index i+3. Verwenden Sie eine While-Schleife, die wiederholt direkt zum nächsten brauchbaren Kandidaten geht.

Hier ist eine leicht verallgemeinerte Implementierung. Es findet das erste Vorkommen von k im Array (vorbehaltlich der Einschränkung +=1) oder -1 wenn es nicht vorkommt:

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

Ausgang:

7 first occurs at index 11
but 9 first "occurs" at index -1

  • Genau das, was ich dachte. Das ist O(N)aber ich glaube nicht, dass es einen schnelleren Weg gibt, dies zu tun.

    – Shapiro Yaacov

    27. Dezember 2015 um 15:12 Uhr


  • Sie könnten es im Durchschnitt etwas schneller machen mit mehr Kandidaten (z. B. erster und letzter) und dann mit demjenigen, der dem Ziel am nächsten ist – das heißt, wenn Sie nur ein einziges Vorkommen finden müssen, nicht das erste.

    – mkadunc

    27. Dezember 2015 um 19:22 Uhr

  • @mkadunc Das ist eine gute Idee. Eine weitere Beobachtung ist, dass, wenn das erste und das letzte Element 7 überspannen, Sie in diesem speziellen Fall eine binäre Suche verwenden können (wenn es Ihnen egal ist, welche 7 Sie finden).

    – John Colemann

    27. Dezember 2015 um 20:40 Uhr

  • Für den Fall, dass Sie 7 (nicht unbedingt die erste) finden müssen, schlage ich die folgende (praktische) Verbesserung vor. Erstellen Sie eine Liste von Abschnitten (zwei Ganzzahlen, „Start“ und „Ende“), und beginnen Sie nicht am Anfang des Arrays, sondern in der Mitte. Je nach Wert in der Zelle ignorieren Sie den relevanten Bereich und fügen die beiden verbleibenden Abschnitte zu Ihrer Abschnittsliste hinzu. Wiederholen Sie dies nun für das nächste Element in der Liste. Dies ist immer noch ‘O(n)’, aber Sie ignorieren jedes Mal, wenn Sie eine Zelle überprüfen, den doppelten Bereich.

    – Shapiro Yaacov

    28. Dezember 2015 um 4:01 Uhr


  • @ShapiroYaacov: In Kombination mit der Überprüfung, ob das Intervall vom niedrigeren zum höheren Wert auf beiden Seiten eines Abschnitts k (7) enthält, verdient dies eine eigene Antwort.

    – Graubart

    28. Dezember 2015 um 7:31 Uhr

Benutzeravatar von Weather Vane
Wetterfahne

Dein Ansatz ist zu kompliziert. Sie müssen nicht jedes Array-Element untersuchen. Der erste Wert ist 4Also 7 ist wenigstens 7-4 Elemente weg, und Sie können diese überspringen.

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

Programmausgabe:

Steps 4, index 11

Bearbeiten: nach Kommentaren von @Martin Zabel verbessert.

  • Du solltest einstellen skip auf den absoluten Unterschied zwischen 7 und array[i].

    – Martin Zabel

    27. Dezember 2015 um 15:17 Uhr

  • @WeatherVane geht davon aus, dass das Element immer im Array gefunden wird, was möglicherweise nicht der Fall ist. -1 ist in diesem Fall eine gültige Rückgabe; was den Code, den Sie haben, ziemlich ändert

    – Eugen

    29. Dezember 2015 um 23:19 Uhr


  • @Eugene falls nicht gefunden, i >= len

    – Wetterfahne

    30. Dezember 2015 um 9:00 Uhr

Eine Variation der herkömmlichen linearen Suche könnte ein guter Weg sein. Lassen Sie uns ein Element auswählen, sagen wir array[i] = 2. Jetzt, array[i + 1] ist entweder 1 oder 3 (ungerade), array[i + 2] wird (nur positive ganze Zahlen) 2 oder 4 (gerade Zahl).

Wenn man so weitermacht, ist ein Muster zu beobachten – array[i + 2*n] enthält gerade Zahlen und daher können alle diese Indizes ignoriert werden.

Auch das können wir sehen

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

also Index i + 5 sollte als nächstes geprüft werden und eine While-Schleife kann verwendet werden, um den nächsten zu prüfenden Index zu bestimmen, abhängig von dem Wert, der bei Index gefunden wird i + 5.

Dies hat zwar Komplexität O(n) (lineare Zeit in Bezug auf die asymptotische Komplexität) ist praktisch besser als eine normale lineare Suche, da nicht alle Indizes besucht werden.

Offensichtlich wird all dies umgekehrt, wenn array[i] (unser Ausgangspunkt) war seltsam.

Benutzeravatar von greybeard
Graubart

Der von John Coleman vorgestellte Ansatz entspricht aller Wahrscheinlichkeit nach dem, was sich der Interviewer erhofft hatte.
Wenn Sie bereit sind, etwas komplizierter vorzugehen, können Sie die erwartete Sprunglänge erhöhen:
Rufen Sie den Zielwert auf k. Beginnen Sie mit dem Wert des ersten Elements v an Stelle p und nenne die Differenz kv dv mit absolutem Wert ein V. Um negative Suchen zu beschleunigen, werfen Sie einen Blick auf das letzte Element als anderen Wert u an Stelle Ö: Wenn dv×du negativ ist, ist k vorhanden (wenn irgendein Vorkommen von k akzeptabel ist, können Sie den Indexbereich hier eingrenzen, wie es die binäre Suche tut). Wenn av+au größer als die Länge des Arrays ist, fehlt k. (Wenn dv×du Null ist, ist v oder u gleich k.)
Auslassen der Indexgültigkeit: Prüfen Sie die (“nächste”) Position, an der die Sequenz zu v mit k in der Mitte zurückkehren könnte: o = p + 2*av.
Wenn dv×du negativ ist, finde k (rekursiv?) von p+av bis o-au;
wenn es Null ist, ist u gleich k bei o.
Wenn du gleich dv ist und der Wert in der Mitte nicht k ist oder au größer als av ist,
oder du scheitern um k von p+av bis o-au zu finden,
Lassen p=o; dv=du; av=au; und probier weiter.
(Für einen vollständigen Rückblick auf die Texte der 60er Jahre, siehe Courier. Mein “erster zweiter Gedanke” war zu verwenden o = p + 2*av - 1was ausschließt du ist gleich dv.)

Benutzeravatar von Akeshwar Jha
Akeshwar Jha

SCHRITT 1

Beginnen Sie mit dem ersten Element und prüfen Sie, ob es 7 ist. Sagen wir c ist der Index der aktuellen Position. Also zunächst c = 0.

SCHRITT 2

Wenn es 7 ist, haben Sie den Index gefunden. Es ist c. Wenn Sie das Ende des Arrays erreicht haben, brechen Sie aus.

SCHRITT 3

Wenn nicht, dann muss mindestens 7 sein |array[c]-7| Positionen entfernt, weil Sie nur eine Einheit pro Index hinzufügen können. Daher hinzufügen |array[c]-7| zu Ihrem aktuellen Index, c, und gehen Sie erneut zu SCHRITT 2, um dies zu überprüfen.

Im schlimmsten Fall, wenn es abwechselnd 1 und -1 gibt, kann die Zeitkomplexität O(n) erreichen, aber durchschnittliche Fälle würden schnell geliefert.

  • Wie unterscheidet sich das von John Colemans Antwort? (Außer raten |c-7| wo |array[c]-7| scheint geboten.)

    – Graubart

    28. Dezember 2015 um 7:50 Uhr


  • Ich habe gerade seine Antwort gesehen. Ich gebe zu, dass die Kernidee dieselbe ist.

    – Akeshwar Jha

    28. Dezember 2015 um 9:01 Uhr


  • Die ursprüngliche Frage besagt nicht, dass das Array mit einer Zahl kleiner als 7 beginnt. Also array[c]-7 kann positiv oder negativ sein. Sie müssen sich bewerben abs() dazu vor dem Sprung nach vorne.

    – arif

    30. Dezember 2015 um 0:32 Uhr

  • Ja, du hast Recht. Deshalb benutze ich array[c] - 7 mit dem Modulo-Operator, |array[c] - 7|.

    – Akeshwar Jha

    30. Dezember 2015 um 7:29 Uhr

Benutzeravatar von John Odom
Johannes Odom

Hier gebe ich die Implementierung in 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;
}

  • Wie unterscheidet sich das von John Colemans Antwort? (Außer raten |c-7| wo |array[c]-7| scheint geboten.)

    – Graubart

    28. Dezember 2015 um 7:50 Uhr


  • Ich habe gerade seine Antwort gesehen. Ich gebe zu, dass die Kernidee dieselbe ist.

    – Akeshwar Jha

    28. Dezember 2015 um 9:01 Uhr


  • Die ursprüngliche Frage besagt nicht, dass das Array mit einer Zahl kleiner als 7 beginnt. Also array[c]-7 kann positiv oder negativ sein. Sie müssen sich bewerben abs() dazu vor dem Sprung nach vorne.

    – arif

    30. Dezember 2015 um 0:32 Uhr

  • Ja, du hast Recht. Deshalb benutze ich array[c] - 7 mit dem Modulo-Operator, |array[c] - 7|.

    – Akeshwar Jha

    30. Dezember 2015 um 7:29 Uhr

Benutzeravatar von Neal Fultz
Neal Fultz

Hier ist eine Lösung im Teile-und-Herrsche-Stil. Auf Kosten von (viel) mehr Buchhaltung können wir mehr Elemente überspringen; Anstatt von links nach rechts zu scannen, testen Sie in der Mitte und springen Sie hinein beide Richtungen.

#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 Ihre Antwort gibt nicht das erste Vorkommen zurück, sondern jedes zufällige Vorkommen des Suchelements, da Sie in der Mitte beginnen und von beiden Seiten überspringen.

    – Widder Patra

    30. Dezember 2015 um 7:03 Uhr


  • Das Umschalten der Rekursionsreihenfolge bleibt dem Leser als Übung überlassen.

    – Neal Fultz

    30. Dezember 2015 um 19:56 Uhr


  • neal-fultz dann bearbeiten Sie bitte die Nachricht in Ihrem printf() Methodenaufruf.

    – Widder Patra

    31. Dezember 2015 um 9:31 Uhr


1419890cookie-checkEffiziente Möglichkeit, ein Element zu suchen

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy