J'ai récemment reçu cette question d'entrevue et je suis curieux de savoir quelle serait une bonne solution.
Disons qu'on me donne un tableau 2d où tous les nombres du tableau sont dans l'ordre croissant de gauche à droite et de haut en bas.
Quelle est la meilleure façon de rechercher et de déterminer si un numéro cible se trouve dans le tableau?
Maintenant, ma première inclination est d'utiliser une recherche binaire puisque mes données sont triées. Je peux déterminer si un nombre est sur une seule ligne en temps O (log N). Cependant, ce sont les 2 directions qui me déconcertent.
Une autre solution qui, selon moi, pourrait fonctionner est de commencer quelque part au milieu. Si la valeur médiane est inférieure à ma cible, je peux être sûr qu'elle se trouve dans la partie carrée gauche de la matrice à partir du milieu. Je me déplace ensuite en diagonale et vérifie à nouveau, réduisant la taille du carré dans lequel la cible pourrait potentiellement se trouver jusqu'à ce que j'aie affiné le nombre de cible.
Quelqu'un at-il de bonnes idées pour résoudre ce problème?
Exemple de tableau:
Trié de gauche à droite, de haut en bas.
1 2 4 5 6
2 3 5 7 8
4 6 8 9 10
5 8 9 10 11
[[1 1][1 1]]
:?Réponses:
Voici une approche simple:
Pour un
NxM
tableau, cela s'exécute dansO(N+M)
. Je pense qu'il serait difficile de faire mieux. :)Edit: Beaucoup de bonnes discussions. Je parlais du cas général ci-dessus; clairement, si
N
ouM
sont petits, vous pouvez utiliser une approche de recherche binaire pour le faire dans quelque chose approchant le temps logarithmique.Voici quelques détails, pour les curieux:
L'histoire
Cet algorithme simple est appelé une recherche Saddleback . Il existe depuis un certain temps, et c'est optimal quand
N == M
. Quelques références:Cependant, lorsque l'
N < M
intuition suggère que la recherche binaire devrait pouvoir faire mieux queO(N+M)
: Par exemple, lorsqueN == 1
, une recherche binaire pure s'exécutera en temps logarithmique plutôt que linéaire.Le pire cas lié
Richard Bird a examiné cette intuition selon laquelle la recherche binaire pourrait améliorer l'algorithme de Saddleback dans un article de 2006:
En utilisant une technique de conversation plutôt inhabituelle, Bird nous montre que pour
N <= M
, ce problème a une borne inférieure deΩ(N * log(M/N))
. Cette limite a du sens, car elle nous donne des performances linéaires quandN == M
et des performances logarithmiques quandN == 1
.Algorithmes pour tableaux rectangulaires
Une approche qui utilise une recherche binaire ligne par ligne ressemble à ceci:
N < M
. Disons que ceN
sont des lignes et desM
colonnes.value
. Si nous le trouvons, nous avons terminé.s
etg
, oùs < value < g
.s
est inférieur àvalue
, nous pouvons donc l'éliminer.g
est plus grand quevalue
, nous pouvons donc l'éliminer.En termes de complexité du pire des cas, cet algorithme
log(M)
fonctionne pour éliminer la moitié des solutions possibles, puis s'appelle récursivement deux fois sur deux problèmes plus petits. Nous devons répéter une version plus petite de celog(M)
travail pour chaque ligne, mais si le nombre de lignes est petit par rapport au nombre de colonnes, le fait de pouvoir éliminer toutes ces colonnes en temps logarithmique commence à en valoir la peine. .Cela donne à l'algorithme une complexité de
T(N,M) = log(M) + 2 * T(M/2, N/2)
ce que Bird montre êtreO(N * log(M/N))
.Une autre approche publiée par Craig Gidney décrit un algorithme similaire à l'approche ci-dessus: il examine une ligne à la fois en utilisant une taille de pas de
M/N
. Son analyse montre que cela entraîne également desO(N * log(M/N))
performances.Comparaison des performances
L'analyse Big-O est bien beau, mais dans quelle mesure ces approches fonctionnent-elles dans la pratique? Le graphique ci-dessous examine quatre algorithmes pour des tableaux de plus en plus «carrés»:
(L'algorithme "naïf" recherche simplement chaque élément du tableau. L'algorithme "récursif" est décrit ci-dessus. L'algorithme "hybride" est une implémentation de l'algorithme de Gidney . Pour chaque taille de tableau, les performances ont été mesurées en chronométrant chaque algorithme sur un ensemble fixe de 1 000 000 de tableaux générés aléatoirement.)
Quelques points notables:
Résumé
Une utilisation intelligente de la recherche binaire peut fournir des
O(N * log(M/N)
performances pour les tableaux rectangulaires et carrés. L'O(N + M)
algorithme "saddleback" est beaucoup plus simple, mais souffre d'une dégradation des performances car les tableaux deviennent de plus en plus rectangulaires.la source
M==N
on veut de laO(N)
complexité, pasO(N*log(N/N))
puisque celle-ci est nulle. Une limite nette "unifiée" correcte estO(N*(log(M/N)+1))
quandN<=M
.Ce problème prend du
Θ(b lg(t))
temps, oùb = min(w,h)
ett=b/max(w,h)
. Je discute de la solution dans ce billet de blog .Borne inférieure
Un adversaire peut forcer un algorithme à effectuer des
Ω(b lg(t))
requêtes, en se limitant à la diagonale principale:Légende: les cellules blanches sont des éléments plus petits, les cellules grises sont des éléments plus grands, les cellules jaunes sont des éléments plus petits ou égaux et les cellules orange sont des éléments plus grands ou égaux. L'adversaire force la solution à être la cellule jaune ou orange que l'algorithme interroge en dernier.
Notez qu'il existe
b
des listes de tailles triées indépendantest
, nécessitantΩ(b lg(t))
élimination complète des requêtes.Algorithme
w >= h
)t
à gauche du coin supérieur droit de la zone validet
cellules de la ligne avec une recherche binaire. Si un élément correspondant est trouvé en faisant cela, retournez avec sa position.t
les colonnes courtes.Trouver un article:
Déterminer qu'un élément n'existe pas:
Légende: les cellules blanches sont des éléments plus petits, les cellules grises sont des éléments plus grands et la cellule verte est un élément égal.
Une analyse
Il y a de
b*t
courtes colonnes à éliminer. Il y a deb
longues rangées à éliminer. Éliminer une longue ligne prend duO(lg(t))
temps. L'éliminationt
des colonnes courtes prend duO(1)
temps.Dans le pire des cas, nous devrons éliminer chaque colonne et chaque ligne, en prenant du temps
O(lg(t)*b + b*t*1/t) = O(b lg(t))
.Notez que je suppose des
lg
pinces à un résultat supérieur à 1 (c'est-à-direlg(x) = log_2(max(2,x))
). C'est pourquoi quandw=h
, c'est-à-diret=1
, nous obtenons la limite attendue deO(b lg(1)) = O(b) = O(w+h)
.Code
la source
O(b*(lg(t)+1))
plutôt queO(b*lg(t))
. Belle rédaction, esp. pour attirer l'attention sur la «technique de l'adversaire» en montrant une borne du «pire des cas».J'utiliserais la stratégie du «diviser pour régner» pour ce problème, semblable à ce que vous avez suggéré, mais les détails sont un peu différents.
Ce sera une recherche récursive sur les sous-plages de la matrice.
À chaque étape, choisissez un élément au milieu de la plage. Si la valeur trouvée correspond à ce que vous recherchez, vous avez terminé.
Sinon, si la valeur trouvée est inférieure à la valeur que vous recherchez, alors vous savez qu'elle n'est pas dans le quadrant au-dessus et à gauche de votre position actuelle. Recherchez donc récursivement les deux sous-plages: tout (exclusivement) en dessous de la position actuelle, et tout (exclusivement) à droite qui est à ou au-dessus de la position actuelle.
Sinon, (la valeur trouvée est supérieure à la valeur que vous recherchez) vous savez qu'elle n'est pas dans le quadrant ci-dessous et à droite de votre position actuelle. Recherchez donc récursivement les deux sous-plages: tout (exclusivement) à gauche de la position actuelle, et tout (exclusivement) au-dessus de la position actuelle qui se trouve sur la colonne courante ou une colonne à droite.
Et ba-da-bing, vous l'avez trouvé.
Notez que chaque appel récursif ne traite que de la sous-plage actuelle, et non (par exemple) de TOUTES les lignes au-dessus de la position actuelle. Juste ceux de la sous-gamme actuelle.
Voici un pseudocode pour vous:
la source
Les deux principales réponses données jusqu'à présent semblent être la
O(log N)
"méthode ZigZag" et laO(N+M)
méthode de recherche binaire. J'ai pensé faire des tests en comparant les deux méthodes avec différentes configurations. Voici les détails:Le tableau est carré N x N dans chaque test, avec N variant de 125 à 8000 (le plus grand tas que ma JVM puisse gérer). Pour chaque taille de tableau, j'ai choisi un endroit aléatoire dans le tableau pour en mettre un seul
2
. J'ai ensuite mis un . Certains des commentateurs précédents semblaient penser que ce type de configuration donnerait le pire des cas d'exécution pour les deux algorithmes. Pour chaque taille de tableau, j'ai choisi 100 emplacements aléatoires différents pour le 2 (cible de recherche) et j'ai exécuté le test. J'ai enregistré le temps d'exécution moyen et le temps d'exécution le plus défavorable pour chaque algorithme. Parce que cela arrivait trop vite pour obtenir de bonnes lectures de ms en Java, et parce que je ne fais pas confiance au nanoTime () de Java, j'ai répété chaque test 1000 fois juste pour ajouter un facteur de biais uniforme à tous les temps. Voici les résultats:3
partout possible (à droite et en dessous du 2) puis rempli le reste du tableau avec1
ZigZag a battu le binaire dans chaque test pour les temps moyens et les pires cas, cependant, ils sont tous plus ou moins dans un ordre de grandeur l'un de l'autre.
Voici le code Java:
la source
Ceci est une brève preuve de la limite inférieure du problème.
Vous ne pouvez pas faire mieux que le temps linéaire (en termes de dimensions du tableau, pas du nombre d'éléments). Dans le tableau ci-dessous, chacun des éléments marqués comme
*
peut être 5 ou 6 (indépendamment des autres). Donc, si votre valeur cible est 6 (ou 5), l'algorithme doit les examiner tous.Bien sûr, cela s'étend également à de plus grands tableaux. Cela signifie que cette réponse est optimale.
Mise à jour: Comme l'a souligné Jeffrey L Whitledge, elle n'est optimale que comme limite inférieure asymptotique sur le temps d'exécution par rapport à la taille des données d'entrée (traitée comme une seule variable). Le temps d'exécution traité comme une fonction à deux variables sur les deux dimensions du tableau peut être amélioré.
la source
Je pense que voici la réponse et cela fonctionne pour tout type de matrice triée
la source
Question interessante. Considérez cette idée - créez une limite où tous les nombres sont supérieurs à votre cible et une autre où tous les nombres sont inférieurs à votre cible. S'il reste quelque chose entre les deux, c'est votre objectif.
Si je recherche 3 dans votre exemple, je lis à travers la première ligne jusqu'à ce que j'atteigne 4, puis cherche le plus petit nombre adjacent (y compris les diagonales) supérieur à 3:
1 2 4 5 6
2 3 5 7 8
4 6 8 9 10
5 8 9 10 11
Maintenant, je fais la même chose pour les nombres inférieurs à 3:
1 2 4 5 6
2 3 5 7 8
4 6 8 9 10
5 8 9 10 11
Maintenant, je demande, y a-t-il quelque chose à l'intérieur des deux limites? Si oui, il doit être 3. Si non, alors il n'y a pas 3. En quelque sorte indirecte puisque je ne trouve pas réellement le nombre, je déduis simplement qu'il doit être là. Cela a l'avantage supplémentaire de compter TOUS les 3.
J'ai essayé cela sur quelques exemples et cela semble fonctionner correctement.
la source
La recherche binaire à travers la diagonale du tableau est la meilleure option. Nous pouvons savoir si l'élément est inférieur ou égal aux éléments de la diagonale.
la source
A. Faites une recherche binaire sur les lignes où le numéro cible pourrait être.
B.Faites-en un graphique: recherchez le nombre en prenant toujours le plus petit nœud voisin non visité et en revenant en arrière lorsqu'un nombre trop grand est trouvé
la source
La recherche binaire serait la meilleure approche, imo. À partir de 1/2 x, 1/2 y le coupera en deux. IE un carré 5x5 serait quelque chose comme x == 2 / y == 3. J'ai arrondi une valeur vers le bas et une valeur vers le haut pour mieux cibler la direction de la valeur ciblée.
Pour plus de clarté, la prochaine itération vous donnerait quelque chose comme x == 1 / y == 2 OU x == 3 / y == 5
la source
Eh bien, pour commencer, supposons que nous utilisons un carré.
1. Recherche d'un carré
J'utiliserais une recherche binaire sur la diagonale. L'objectif est de localiser le plus petit nombre qui n'est pas strictement inférieur au nombre cible.
Disons que je cherche
4
, par exemple, je finirais localiser5
à(2,2)
.Ensuite, je suis assuré que si
4
est dans le tableau, il est à une position soit(x,2)
ou(2,x)
avecx
dans[0,2]
. Eh bien, ce n'est que 2 recherches binaires.La complexité n'est pas décourageante:
O(log(N))
(3 recherches binaires sur des plages de longueurN
)2. Recherche d'un rectangle, approche naïve
Bien sûr, cela devient un peu plus compliqué quand
N
etM
différer (avec un rectangle), considérez ce cas dégénéré:Et disons que je cherche
9
... L'approche diagonale est toujours bonne, mais la définition des changements diagonaux. Voici ma diagonale[1, (5 or 6), 17]
. Disons que j'ai ramassé[1,5,17]
, alors je sais que si9
est dans le tableau, c'est soit dans la sous-partie:Cela nous donne 2 rectangles:
Alors on peut récurer! probablement en commençant par celui avec le moins d'éléments (bien que dans ce cas, cela nous tue).
Je dois souligner que si l'une des dimensions est inférieure à
3
, nous ne pouvons pas appliquer les méthodes diagonales et devons utiliser une recherche binaire. Ici, cela signifierait:10 11 12 13 14 15 16
, non trouvé5 6 7 8
, non trouvé6 7 8 9
, non trouvéC'est délicat car pour obtenir de bonnes performances, vous voudrez peut-être différencier plusieurs cas, en fonction de la forme générale ...
3. Recherche d'un rectangle, approche brutale
Ce serait beaucoup plus facile si nous nous occupions d'un carré ... alors concilions les choses.
Nous avons maintenant un carré.
Bien sûr, nous ne créerons probablement PAS réellement ces lignes, nous pourrions simplement les émuler.
donc ça se comporte comme un carré sans occuper plus de mémoire (au prix de la vitesse, probablement, en fonction du cache ... eh bien: p)
la source
ÉDITER:
J'ai mal compris la question. Comme le soulignent les commentaires, cela ne fonctionne que dans le cas le plus restreint.
Dans un langage comme C qui stocke les données dans l'ordre des lignes principales, traitez-le simplement comme un tableau 1D de taille n * m et utilisez une recherche binaire.
la source
J'ai une solution récursive Divide & Conquer. L'idée de base pour une étape est la suivante: Nous savons que le gauche-haut (LU) est le plus petit et le bas à droite (RB) est le plus grand nombre, donc le No (N) donné doit: N> = LU et N <= RB
IF N == LU et N == RB :::: Élément trouvé et abandon renvoyant la position / Index Si N> = LU et N <= RB = FALSE, No n'est pas là et abandonne. Si N> = LU et N <= RB = TRUE, divisez le tableau 2D en 4 parties égales de tableau 2D chacun de manière logique .. Et puis appliquez la même étape d'algo aux quatre sous-tableaux.
Mon Algo est correct J'ai implémenté sur le PC de mes amis. Complexité: chacune des 4 comparaisons peut être utilisée pour déduire le nombre total d'éléments à un quart dans le pire des cas .. Donc ma complexité devient 1 + 4 x lg (n) + 4 Mais vraiment prévu que cela fonctionne sur O (n)
Je pense que quelque chose ne va pas quelque part dans mon calcul de la complexité, veuillez corriger si oui.
la source
La solution optimale est de commencer par le coin supérieur gauche, qui a une valeur minimale. Déplacez-vous en diagonale vers le bas vers la droite jusqu'à ce que vous atteigniez un élément dont la valeur> = valeur de l'élément donné. Si la valeur de l'élément est égale à celle de l'élément donné, renvoie la valeur true.
Sinon, à partir de là, nous pouvons procéder de deux manières.
Stratégie 1:
Stratégie 2: Soit i l'index de ligne et j l'index de colonne de l'élément diagonal auquel nous nous sommes arrêtés. (Ici, nous avons i = j, BTW). Soit k = 1.
1 2 4 5 6
2 3 5 7 8
4 6 8 9 10
5 8 9 10 11
la source
la source
Je suggère de stocker tous les caractères dans un fichier
2D list
. puis recherchez l'index de l'élément requis s'il existe dans la liste.S'il n'est pas présent, imprimez le message approprié sinon imprimez la ligne et la colonne comme suit:
row = (index/total_columns)
etcolumn = (index%total_columns -1)
Cela entraînera uniquement le temps de recherche binaire dans une liste.
Veuillez suggérer des corrections. :)
la source
Si la solution O (M log (N)) est correcte pour un tableau MxN -
Démo de travail C ++.
S'il vous plaît faites-moi savoir si cela ne fonctionnera pas ou s'il y a un bogue.
la source
Je pose cette question lors d'entretiens depuis près d'une décennie et je pense qu'il n'y a qu'une seule personne qui a pu trouver un algorithme optimal.
Ma solution a toujours été:
Recherche binaire la diagonale du milieu, qui est la diagonale descendant et droite, contenant l'élément à
(rows.count/2, columns.count/2)
.Si le numéro cible est trouvé, renvoie vrai.
Sinon, deux nombres (
u
etv
) auront été trouvés tels qu'ilsu
sont plus petits que la cible,v
plus grands que la cible, etv
un à droite et un en bas deu
.Rechercher récursivement la sous-matrice à droite
u
et en haut dev
et celle en basu
et à gauche dev
.Je pense que c'est une amélioration stricte par rapport à l'algorithme donné par Nate ici , car la recherche de la diagonale permet souvent une réduction de plus de la moitié de l'espace de recherche (si la matrice est proche du carré), alors que la recherche d'une ligne ou d'une colonne entraîne toujours une élimination d'exactement la moitié.
Voici le code de Swift (probablement pas terriblement Swifty):
la source
Étant donné une matrice carrée comme suit:
On sait que a <c, d <f, i <k. Ce que nous ne savons pas, c'est si d <c ou d> c, etc. Nous n'avons des garanties qu'en 1 dimension.
En regardant les éléments de fin (c, f, k), on peut faire une sorte de filtre: est-ce que N <c? recherche (): suivant (). Ainsi, nous avons n itérations sur les lignes, chaque ligne prenant soit O (log (n)) pour la recherche binaire, soit O (1) si filtré.
Laissez-moi donner un EXEMPLE où N = j,
Réessayez avec N = q,
Il existe probablement une meilleure solution, mais c'est facile à expliquer .. :)
la source
Comme il s'agit d'une question d'entretien, elle semble conduire à une discussion sur la programmation parallèle et les algorithmes de réduction de carte .
Voir http://code.google.com/intl/de/edu/parallel/mapreduce-tutorial.html
la source