Vor kurzem wurde mir diese Interviewfrage gestellt und ich bin gespannt, was für eine gute Lösung das wäre.
Angenommen, ich erhalte ein 2D-Array, in dem alle Zahlen im Array von links nach rechts und von oben nach unten in aufsteigender Reihenfolge aufgeführt sind.
Was ist der beste Weg, um zu suchen und festzustellen, ob sich eine Zielnummer im Array befindet?
Meine erste Neigung besteht nun darin, eine binäre Suche zu verwenden, da meine Daten sortiert sind. Ich kann in O (log N) feststellen, ob sich eine Zahl in einer einzelnen Zeile befindet. Es sind jedoch die 2 Richtungen, die mich abschrecken.
Eine andere Lösung, von der ich dachte, dass sie funktionieren könnte, besteht darin, irgendwo in der Mitte zu beginnen. Wenn der Mittelwert kleiner als mein Ziel ist, kann ich sicher sein, dass er sich im linken quadratischen Teil der Matrix von der Mitte befindet. Ich bewege mich dann diagonal und überprüfe erneut, wobei ich die Größe des Quadrats reduziere, in dem sich das Ziel möglicherweise befinden könnte, bis ich die Zielnummer verfeinert habe.
Hat jemand gute Ideen zur Lösung dieses Problems?
Beispielarray:
Von links nach rechts, von oben nach unten sortiert.
1 2 4 5 6
2 3 5 7 8
4 6 8 9 10
5 8 9 10 11
[[1 1][1 1]]
?Antworten:
Hier ist ein einfacher Ansatz:
Für ein
NxM
Array wird dies ausgeführtO(N+M)
. Ich denke, es wäre schwierig, es besser zu machen. :) :)Edit: Viele gute Diskussionen. Ich habe über den allgemeinen Fall oben gesprochen; Wenn Sie klein sind
N
oderM
sind, können Sie einen binären Suchansatz verwenden, um dies in einer Zeit zu tun, die sich der logarithmischen Zeit nähert.Hier sind einige Details für diejenigen, die neugierig sind:
Geschichte
Dieser einfache Algorithmus wird als Saddleback-Suche bezeichnet . Es gibt es schon eine Weile und es ist optimal, wenn
N == M
. Einige Referenzen:Wenn jedoch die
N < M
Intuition vorschlägt, dass die binäre Suche besser funktionieren sollte alsO(N+M)
: Zum Beispiel, wennN == 1
eine reine binäre Suche eher in logarithmischer als in linearer Zeit ausgeführt wird.Worst-Case gebunden
Richard Bird untersuchte diese Intuition, dass die binäre Suche den Saddleback-Algorithmus verbessern könnte, in einem Artikel aus dem Jahr 2006:
Mit einer eher ungewöhnlichen Konversationstechnik zeigt uns Bird, dass
N <= M
dieses Problem eine Untergrenze von hatΩ(N * log(M/N))
. Diese Grenze ist sinnvoll, da sie uns eine lineare Leistung wannN == M
und eine logarithmische Leistung wann gibtN == 1
.Algorithmen für rechteckige Arrays
Ein Ansatz, der eine zeilenweise binäre Suche verwendet, sieht folgendermaßen aus:
N < M
. Angenommen, esN
handelt sich um Zeilen undM
Spalten.value
. Wenn wir es finden, sind wir fertig.s
undg
, wos < value < g
.s
ist kleiner alsvalue
, sodass wir es entfernen können.g
ist größer alsvalue
, sodass wir es entfernen können.In Bezug auf die Komplexität im schlimmsten Fall
log(M)
eliminiert dieser Algorithmus die Hälfte der möglichen Lösungen und ruft sich dann bei zwei kleineren Problemen zweimal rekursiv auf. Wir müssen eine kleinere Version dieserlog(M)
Arbeit für jede Zeile wiederholen , aber wenn die Anzahl der Zeilen im Vergleich zur Anzahl der Spalten gering ist, lohnt es sich, alle diese Spalten in logarithmischer Zeit zu entfernen .Dies gibt dem Algorithmus eine Komplexität
T(N,M) = log(M) + 2 * T(M/2, N/2)
, die Bird zeigtO(N * log(M/N))
.Ein anderer von Craig Gidney veröffentlichter Ansatz beschreibt einen Algorithmus, der dem obigen Ansatz ähnlich ist: Er untersucht jeweils eine Zeile mit einer Schrittgröße von
M/N
. Seine Analyse zeigt, dass dies auch zuO(N * log(M/N))
Leistung führt.Leistungsvergleich
Die Big-O-Analyse ist gut und schön, aber wie gut funktionieren diese Ansätze in der Praxis? In der folgenden Tabelle werden vier Algorithmen für zunehmend "quadratische" Arrays untersucht:
(Der "naive" Algorithmus durchsucht einfach jedes Element des Arrays. Der "rekursive" Algorithmus ist oben beschrieben. Der "hybride" Algorithmus ist eine Implementierung des Gidney-Algorithmus . Für jede Arraygröße wurde die Leistung gemessen, indem jeder Algorithmus über einen festen Satz gesteuert wurde von 1.000.000 zufällig generierten Arrays.)
Einige bemerkenswerte Punkte:
Zusammenfassung
Die clevere Verwendung der binären Suche kann sowohl
O(N * log(M/N)
für rechteckige als auch für quadratische Arrays Leistung bringen. DerO(N + M)
"Saddleback" -Algorithmus ist viel einfacher, leidet jedoch unter Leistungseinbußen, da Arrays zunehmend rechteckiger werden.quelle
M==N
wir jedochO(N)
Komplexität wollen , nicht,O(N*log(N/N))
da letztere Null ist. Eine korrekte "einheitliche" scharfe Grenze istO(N*(log(M/N)+1))
wannN<=M
.Dieses Problem braucht
Θ(b lg(t))
Zeit, wob = min(w,h)
undt=b/max(w,h)
. Ich diskutiere die Lösung in diesem Blog-Beitrag .Untergrenze
Ein Gegner kann einen Algorithmus zwingen,
Ω(b lg(t))
Abfragen durchzuführen, indem er sich auf die Hauptdiagonale beschränkt:Legende: Weiße Zellen sind kleinere Elemente, graue Zellen sind größere Elemente, gelbe Zellen sind kleinere oder gleiche Elemente und orange Zellen sind größere oder gleiche Elemente. Der Gegner erzwingt, dass die Lösung die gelbe oder orangefarbene Zelle ist, die der Algorithmus zuletzt abfragt.
Beachten Sie, dass es
b
unabhängige sortierte Größenlisten gibtt
, bei denenΩ(b lg(t))
Abfragen vollständig entfernt werden müssen.Algorithmus
w >= h
)t
links von der oberen rechten Ecke des gültigen Bereichst
Zellen in der Zeile mit einer binären Suche. Wenn dabei ein passender Artikel gefunden wird, kehren Sie mit seiner Position zurück.t
kurze Spalten eliminiert werden .Einen Gegenstand finden:
Bestimmen eines Elements existiert nicht:
Legende: Weiße Zellen sind kleinere Elemente, graue Zellen sind größere Elemente und die grüne Zelle ist ein gleiches Element.
Analyse
Es sind
b*t
kurze Spalten zu entfernen. Es sindb
lange Reihen zu beseitigen. Das Eliminieren einer langen Reihe kostetO(lg(t))
Zeit. Das Eliminierent
kurzer Spalten kostetO(1)
Zeit.Im schlimmsten Fall müssen wir jede Spalte und jede Zeile entfernen, was einige Zeit in Anspruch nimmt
O(lg(t)*b + b*t*1/t) = O(b lg(t))
.Beachten Sie, dass ich
lg
Klammern für ein Ergebnis über 1 (dhlg(x) = log_2(max(2,x))
) annehme . Das ist der Grundw=h
, warumt=1
wir, wenn wir das tun, die erwartete Grenze bekommenO(b lg(1)) = O(b) = O(w+h)
.Code
quelle
O(b*(lg(t)+1))
statt angegeben werdenO(b*lg(t))
. Nettes Schreiben, insb. um auf die "gegnerische Technik" aufmerksam zu machen, indem sie eine "Worst-Case" -Bindung zeigt.Ich würde die Divide-and-Conquer-Strategie für dieses Problem verwenden, ähnlich wie Sie es vorgeschlagen haben, aber die Details sind etwas anders.
Dies ist eine rekursive Suche nach Unterbereichen der Matrix.
Wählen Sie bei jedem Schritt ein Element in der Mitte des Bereichs aus. Wenn der gefundene Wert das ist, was Sie suchen, sind Sie fertig.
Wenn der gefundene Wert geringer ist als der gesuchte Wert, wissen Sie, dass er sich nicht im Quadranten über und links von Ihrer aktuellen Position befindet. Suchen Sie also rekursiv die beiden Unterbereiche: alles (ausschließlich) unter der aktuellen Position und alles (ausschließlich) rechts, das sich an oder über der aktuellen Position befindet.
Andernfalls (der gefundene Wert ist größer als der gesuchte Wert) wissen Sie, dass er sich nicht im Quadranten unterhalb und rechts von Ihrer aktuellen Position befindet. Suchen Sie also rekursiv die beiden Unterbereiche: alles (ausschließlich) links von der aktuellen Position und alles (ausschließlich) über der aktuellen Position in der aktuellen Spalte oder eine Spalte rechts.
Und ba-da-bing, du hast es gefunden.
Beachten Sie, dass jeder rekursive Aufruf nur den aktuellen Unterbereich behandelt, nicht (zum Beispiel) ALLE Zeilen über der aktuellen Position. Nur die im aktuellen Unterbereich.
Hier ist ein Pseudocode für Sie:
quelle
Die beiden bisherigen Hauptantworten scheinen die wohl
O(log N)
"ZigZag-Methode" und dieO(N+M)
binäre Suchmethode zu sein. Ich dachte, ich würde einige Tests durchführen, um die beiden Methoden mit verschiedenen Setups zu vergleichen. Hier sind die Details:Das Array ist in jedem Test N x N Quadrat, wobei N zwischen 125 und 8000 variiert (der größte, den mein JVM-Heap verarbeiten kann). Für jede Arraygröße habe ich eine zufällige Stelle im Array ausgewählt, um eine einzelne zu platzieren
2
. Ich habe dann ein3
überall mögliches (rechts und unterhalb der 2) platziert und dann den Rest des Arrays mit gefüllt1
. Einige der früheren Kommentatoren schienen zu glauben, dass diese Art der Einrichtung für beide Algorithmen eine Worst-Case-Laufzeit ergeben würde. Für jede Arraygröße habe ich 100 verschiedene zufällige Positionen für die 2 (Suchziel) ausgewählt und den Test durchgeführt. Ich habe für jeden Algorithmus die durchschnittliche Laufzeit und die Worst-Case-Laufzeit aufgezeichnet. Da es zu schnell ging, um gute MS-Werte in Java zu erhalten, und weil ich Javas nanoTime () nicht vertraue, habe ich jeden Test 1000 Mal wiederholt, um immer einen einheitlichen Bias-Faktor hinzuzufügen. Hier sind die Ergebnisse:ZigZag schlug Binär in jedem Test sowohl für die Durchschnitts- als auch für die Worst-Case-Zeiten, sie liegen jedoch alle mehr oder weniger in einer Größenordnung voneinander.
Hier ist der Java-Code:
quelle
Dies ist ein kurzer Beweis für die Untergrenze des Problems.
Sie können es nicht besser machen als die lineare Zeit (in Bezug auf die Array-Dimensionen, nicht die Anzahl der Elemente). Im folgenden Array kann jedes der als
*
5 oder 6 gekennzeichneten Elemente (unabhängig von anderen) sein. Wenn Ihr Zielwert also 6 (oder 5) ist, muss der Algorithmus alle untersuchen.Dies erweitert sich natürlich auch auf größere Arrays. Dies bedeutet, dass diese Antwort optimal ist.
Update: Wie von Jeffrey L Whitledge hervorgehoben, ist es nur als asymptotische Untergrenze der Laufzeit gegenüber der Größe der Eingabedaten (als einzelne Variable behandelt) optimal. Die Laufzeit, die in beiden Array-Dimensionen als Funktion mit zwei Variablen behandelt wird, kann verbessert werden.
quelle
Ich denke, hier ist die Antwort und sie funktioniert für jede Art von sortierter Matrix
quelle
Interessante Frage. Betrachten Sie diese Idee - erstellen Sie eine Grenze, an der alle Zahlen größer als Ihr Ziel sind, und eine andere, an der alle Zahlen kleiner als Ihr Ziel sind. Wenn noch etwas zwischen den beiden übrig ist, ist das Ihr Ziel.
Wenn ich in Ihrem Beispiel nach 3 suche, lese ich in der ersten Zeile, bis ich 4 drücke, und suche dann nach der kleinsten benachbarten Zahl (einschließlich Diagonalen) größer als 3:
1 2 4 5 6
2 3 5 7 8
4 6 8 9 10
5 8 9 10 11
Jetzt mache ich dasselbe für Zahlen unter 3:
1 2 4 5 6
2 3 5 7 8
4 6 8 9 10
5 8 9 10 11
Jetzt frage ich, liegt etwas innerhalb der beiden Grenzen? Wenn ja, muss es 3 sein. Wenn nein, dann gibt es keine 3. Art von indirekt, da ich die Nummer nicht wirklich finde, schließe ich nur, dass sie dort sein muss. Dies hat den zusätzlichen Bonus, ALLE 3 zu zählen.
Ich habe dies an einigen Beispielen versucht und es scheint in Ordnung zu funktionieren.
quelle
Die binäre Suche durch die Diagonale des Arrays ist die beste Option. Wir können herausfinden, ob das Element kleiner oder gleich den Elementen in der Diagonale ist.
quelle
A. Führen Sie eine binäre Suche in den Zeilen durch, in denen sich möglicherweise die Zielnummer befindet.
B. Machen Sie es zu einem Diagramm: Suchen Sie nach der Nummer, indem Sie immer den kleinsten nicht besuchten Nachbarknoten nehmen und zurückverfolgen, wenn eine zu große Nummer gefunden wird
quelle
Binäre Suche wäre der beste Ansatz, imo. Ab 1/2 x schneidet 1/2 y es in zwei Hälften. IE ein 5x5 Quadrat wäre so etwas wie x == 2 / y == 3. Ich habe einen Wert nach unten und einen Wert nach oben gerundet, um eine bessere Zone in Richtung des Zielwerts zu erreichen.
Aus Gründen der Klarheit würde die nächste Iteration so etwas wie x == 1 / y == 2 ODER x == 3 / y == 5 ergeben
quelle
Nehmen wir zunächst an, wir verwenden ein Quadrat.
1. Ein Quadrat suchen
Ich würde eine binäre Suche auf der Diagonale verwenden. Das Ziel ist es, die kleinere Zahl zu lokalisieren, die nicht streng niedriger als die Zielzahl ist.
Angenommen, ich suche zum
4
Beispiel, dann würde ich am Ende5
bei suchen(2,2)
.Dann bin ich mir sicher, dass wenn
4
es in der Tabelle ist, es an einer Position entweder(x,2)
oder(2,x)
mitx
in ist[0,2]
. Nun, das sind nur 2 binäre Suchen.Die Komplexität ist nicht entmutigend:
O(log(N))
(3 binäre Suchen nach LängenbereichenN
)2. Suche nach einem rechteckigen, naiven Ansatz
Natürlich wird es etwas komplizierter, wenn
N
undM
unterscheiden Sie sich (mit einem Rechteck), betrachten Sie diesen entarteten Fall:Nehmen wir an, ich suche
9
... Der diagonale Ansatz ist immer noch gut, aber die Definition von diagonalen Änderungen. Hier ist meine Diagonale[1, (5 or 6), 17]
. Nehmen wir an, ich habe abgeholt[1,5,17]
, dann weiß ich, dass wenn9
es in der Tabelle ist, es entweder im Unterabschnitt ist:Dies gibt uns 2 Rechtecke:
So können wir zurückgreifen! wahrscheinlich beginnend mit dem mit weniger Elementen (obwohl es uns in diesem Fall umbringt).
Ich sollte darauf hinweisen, dass
3
wir , wenn eine der Dimensionen kleiner als ist , die diagonalen Methoden nicht anwenden können und eine binäre Suche verwenden müssen. Hier würde es bedeuten:10 11 12 13 14 15 16
, nicht gefunden5 6 7 8
, nicht gefunden6 7 8 9
, nicht gefundenEs ist schwierig, denn um eine gute Leistung zu erzielen, sollten Sie je nach allgemeiner Form zwischen mehreren Fällen unterscheiden.
3. Suche nach einem rechteckigen, brutalen Ansatz
Es wäre viel einfacher, wenn wir uns mit einem Quadrat befassen würden ... also lasst uns einfach die Dinge zusammenfassen.
Wir haben jetzt ein Quadrat.
Natürlich werden wir diese Zeilen wahrscheinlich NICHT erstellen, wir könnten sie einfach emulieren.
es verhält sich also wie ein Quadrat, ohne mehr Speicher zu belegen (auf Kosten der Geschwindigkeit, wahrscheinlich abhängig vom Cache ... na ja: p)
quelle
BEARBEITEN:
Ich habe die Frage falsch verstanden. Wie die Kommentare zeigen, funktioniert dies nur im eingeschränkteren Fall.
In einer Sprache wie C, in der Daten in Zeilenreihenfolge gespeichert werden, behandeln Sie sie einfach als 1D-Array der Größe n * m und verwenden Sie eine binäre Suche.
quelle
Ich habe eine rekursive Divide & Conquer-Lösung. Grundidee für einen Schritt ist: Wir wissen, dass das linke obere (LU) am kleinsten und das rechte untere (RB) die größte Nr. Ist, daher muss das gegebene Nein (N): N> = LU und N <= sein RB
WENN N == LU und N == RB :::: Element gefunden und abgebrochen, wobei die Position / der Index zurückgegeben wird Wenn N> = LU und N <= RB = FALSE, ist Nein nicht vorhanden und wird abgebrochen. Wenn N> = LU und N <= RB = TRUE, teilen Sie das 2D-Array auf logische Weise in 4 gleiche Teile des 2D-Arrays. Wenden Sie dann denselben Algo-Schritt auf alle vier Sub-Arrays an.
Mein Algo ist korrekt Ich habe es auf dem PC meines Freundes implementiert. Komplexität: Jede 4 Vergleiche kann verwendet werden, um die Gesamtzahl der Elemente im schlimmsten Fall auf ein Viertel abzuleiten. Meine Komplexität beträgt also 1 + 4 x lg (n) + 4. Aber ich habe wirklich erwartet, dass dies auf O funktioniert (n)
Ich denke, irgendwo in meiner Berechnung der Komplexität stimmt etwas nicht. Bitte korrigieren Sie dies, wenn ja.
quelle
Die optimale Lösung besteht darin, an der oberen linken Ecke zu beginnen, die nur einen minimalen Wert hat. Bewegen Sie sich diagonal nach rechts unten, bis Sie auf ein Element treffen, dessen Wert> = Wert des angegebenen Elements ist. Wenn der Wert des Elements dem des angegebenen Elements entspricht, wird return als true gefunden.
Ansonsten können wir von hier aus auf zwei Arten vorgehen.
Strategie 1:
Strategie 2: Ich bezeichne den Zeilenindex und j den Spaltenindex des diagonalen Elements, bei dem wir angehalten haben. (Hier haben wir i = j, BTW). Sei k = 1.
1 2 4 5 6
2 3 5 7 8
4 6 8 9 10
5 8 9 10 11
quelle
quelle
Ich schlage vor, alle Zeichen in einem zu speichern
2D list
. Suchen Sie dann den Index des erforderlichen Elements, falls es in der Liste vorhanden ist.Wenn nicht vorhanden, drucken Sie die entsprechende Nachricht. Andernfalls drucken Sie Zeile und Spalte wie folgt:
row = (index/total_columns)
undcolumn = (index%total_columns -1)
Dies führt nur zur binären Suchzeit in einer Liste.
Bitte schlagen Sie Korrekturen vor. :) :)
quelle
Wenn O (M log (N)) - Lösung für ein MxN-Array in Ordnung ist -
Funktionierende C ++ - Demo.
Bitte lassen Sie mich wissen, ob dies nicht funktionieren würde oder ob es einen Fehler gibt.
quelle
Ich habe diese Frage fast ein Jahrzehnt lang in Interviews gestellt und ich denke, es gab nur eine Person, die in der Lage war, einen optimalen Algorithmus zu entwickeln.
Meine Lösung war schon immer:
Binäre Suche in der mittleren Diagonale, dh der Diagonale, die nach unten und rechts verläuft und das Element bei enthält
(rows.count/2, columns.count/2)
.Wenn die Zielnummer gefunden wird, geben Sie true zurück.
Andernfalls wurden zwei Zahlen (
u
undv
) gefunden,u
die kleiner als das Ziel,v
größer als das Ziel undv
eine rechts und eine niedriger als das Ziel sindu
.Durchsuchen Sie rekursiv die Untermatrix rechts
u
und obenv
und die Untermatrix untenu
und links vonv
.Ich glaube, dies ist eine strikte Verbesserung gegenüber dem von Nate hier angegebenen Algorithmus , da das Durchsuchen der Diagonale häufig eine Reduzierung des Suchraums um mehr als die Hälfte ermöglicht (wenn die Matrix nahe am Quadrat liegt), während das Durchsuchen einer Zeile oder Spalte immer zu einer Eliminierung führt von genau der Hälfte.
Hier ist der Code in (wahrscheinlich nicht besonders schnell) Swift:
quelle
Gegeben ist eine quadratische Matrix wie folgt:
Wir wissen, dass a <c, d <f, i <k. Was wir nicht wissen, ist, ob dc oder dc usw. Wir haben Garantien nur in 1-Dimension.
Wenn wir uns die Endelemente (c, f, k) ansehen, können wir eine Art Filter erstellen: Ist N <c? search (): next (). Wir haben also n Iterationen über die Zeilen, wobei jede Zeile entweder O (log (n)) für die binäre Suche oder O (1) nimmt, wenn sie herausgefiltert wird.
Lassen Sie mich ein BEISPIEL geben, in dem N = j,
Versuchen Sie es erneut mit N = q,
Es gibt wahrscheinlich eine bessere Lösung, aber das ist leicht zu erklären .. :)
quelle
Da es sich um eine Interviewfrage handelt, scheint dies zu einer Diskussion über parallele Programmierung und Map-Reduction- Algorithmen zu führen.
Siehe http://code.google.com/intl/de/edu/parallel/mapreduce-tutorial.html
quelle