Comment rechercher un nombre dans un tableau 2D trié de gauche à droite et de haut en bas?

90

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  
Phukab
la source
Question simple: peut-être que vous pouvez avoir un voisin avec la même valeur [[1 1][1 1]]:?
Matthieu M.

Réponses:

115

Voici une approche simple:

  1. Commencez dans le coin inférieur gauche.
  2. Si la cible est inférieure à cette valeur, elle doit être au-dessus de nous, alors montez d'un .
  3. Sinon, nous savons que la cible ne peut pas être dans cette colonne, alors déplacez-vous vers la droite .
  4. Aller à 2.

Pour un NxMtableau, cela s'exécute dans O(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 NouM 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 < Mintuition suggère que la recherche binaire devrait pouvoir faire mieux que O(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 quand N == Met des performances logarithmiques quand N == 1.

Algorithmes pour tableaux rectangulaires

Une approche qui utilise une recherche binaire ligne par ligne ressemble à ceci:

  1. Commencez par un tableau rectangulaire où N < M. Disons que ce Nsont des lignes et des Mcolonnes.
  2. Effectuez une recherche binaire sur la ligne du milieu pour value. Si nous le trouvons, nous avons terminé.
  3. Sinon, nous avons trouvé une paire adjacente de nombres set g, où s < value < g.
  4. Le rectangle de nombres au-dessus et à gauche de sest inférieur à value, nous pouvons donc l'éliminer.
  5. Le rectangle ci-dessous et à droite de gest plus grand que value, nous pouvons donc l'éliminer.
  6. Passez à l'étape (2) pour chacun des deux rectangles restants.

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 ce log(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 être O(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 des O(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»:

performances de l'algorithme par rapport à l'équerrage

(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:

  • Comme prévu, les algorithmes de "recherche binaire" offrent les meilleures performances sur les tableaux rectangulaires et l'algorithme de Saddleback fonctionne le mieux sur les tableaux carrés.
  • L'algorithme de Saddleback fonctionne moins bien que l'algorithme «naïf» pour les tableaux 1-d, probablement parce qu'il effectue plusieurs comparaisons sur chaque élément.
  • La perte de performance que les algorithmes de «recherche binaire» prennent sur les tableaux carrés est vraisemblablement due à la surcharge de l'exécution de recherches binaires répétées.

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.

Nate Kohl
la source
6
appliquez la recherche binaire à la marche diagonale et vous obtenez O (logN) ou O (logM) selon la valeur la plus élevée.
Anurag
3
@Anurag - Je ne pense pas que la complexité fonctionne aussi bien. Une recherche binaire vous donnera un bon point de départ, mais vous devrez parcourir une dimension ou l'autre jusqu'au bout, et dans le pire des cas, vous pourriez toujours commencer dans un coin et finir dans l'autre.
Jeffrey L Whitledge
1
Si N = 1 et M = 1000000 je peux faire mieux que O (N + M), donc une autre solution consiste à appliquer une recherche binaire dans chaque ligne qui amène O (N * log (M)) où N <M au cas où cela donnerait plus petite constante.
Luka Rahne
1
J'ai fait quelques tests en utilisant à la fois votre méthode et la méthode de recherche binaire et j'ai publié les résultats ICI . Il semble que la méthode en zigzag soit la meilleure, sauf si je n'ai pas réussi à générer correctement les pires conditions pour les deux méthodes.
The111
1
Belle utilisation des références! Cependant quand M==Non veut de la O(N)complexité, pas O(N*log(N/N))puisque celle-ci est nulle. Une limite nette "unifiée" correcte est O(N*(log(M/N)+1))quand N<=M.
hardmath
35

Ce problème prend du Θ(b lg(t))temps, où b = min(w,h)et t=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:

Adversaire utilisant 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 bdes listes de tailles triées indépendantes t, nécessitantΩ(b lg(t)) élimination complète des requêtes.

Algorithme

  1. (Supposons sans perte de généralité que w >= h )
  2. Comparez l'élément cible à la cellule t à gauche du coin supérieur droit de la zone valide
    • Si l'élément de la cellule correspond, renvoie la position actuelle.
    • Si l'élément de la cellule est inférieur à l'élément cible, éliminez le reste t cellules de la ligne avec une recherche binaire. Si un élément correspondant est trouvé en faisant cela, retournez avec sa position.
    • Sinon, l'élément de la cellule est plus que l'élément cible, ce qui élimine tles colonnes courtes.
  3. S'il n'y a plus de zone valide, retourne l'échec
  4. Aller à l'étape 2

Trouver un article:

Trouver un article

Déterminer qu'un élément n'existe pas:

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*tcourtes colonnes à éliminer. Il y a de blongues rangées à éliminer. Éliminer une longue ligne prend du O(lg(t))temps. L'élimination tdes colonnes courtes prend du O(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 lgpinces à un résultat supérieur à 1 (c'est-à-dire lg(x) = log_2(max(2,x))). C'est pourquoi quand w=h, c'est-à-dire t=1, nous obtenons la limite attendue de O(b lg(1)) = O(b) = O(w+h).

Code

public static Tuple<int, int> TryFindItemInSortedMatrix<T>(this IReadOnlyList<IReadOnlyList<T>> grid, T item, IComparer<T> comparer = null) {
    if (grid == null) throw new ArgumentNullException("grid");
    comparer = comparer ?? Comparer<T>.Default;

    // check size
    var width = grid.Count;
    if (width == 0) return null;
    var height = grid[0].Count;
    if (height < width) {
        var result = grid.LazyTranspose().TryFindItemInSortedMatrix(item, comparer);
        if (result == null) return null;
        return Tuple.Create(result.Item2, result.Item1);
    }

    // search
    var minCol = 0;
    var maxRow = height - 1;
    var t = height / width;
    while (minCol < width && maxRow >= 0) {
        // query the item in the minimum column, t above the maximum row
        var luckyRow = Math.Max(maxRow - t, 0);
        var cmpItemVsLucky = comparer.Compare(item, grid[minCol][luckyRow]);
        if (cmpItemVsLucky == 0) return Tuple.Create(minCol, luckyRow);

        // did we eliminate t rows from the bottom?
        if (cmpItemVsLucky < 0) {
            maxRow = luckyRow - 1;
            continue;
        }

        // we eliminated most of the current minimum column
        // spend lg(t) time eliminating rest of column
        var minRowInCol = luckyRow + 1;
        var maxRowInCol = maxRow;
        while (minRowInCol <= maxRowInCol) {
            var mid = minRowInCol + (maxRowInCol - minRowInCol + 1) / 2;
            var cmpItemVsMid = comparer.Compare(item, grid[minCol][mid]);
            if (cmpItemVsMid == 0) return Tuple.Create(minCol, mid);
            if (cmpItemVsMid > 0) {
                minRowInCol = mid + 1;
            } else {
                maxRowInCol = mid - 1;
                maxRow = mid - 1;
            }
        }

        minCol += 1;
    }

    return null;
}
Craig Gidney
la source
1
Intéressant et peut-être partiellement au-dessus de ma tête. Je ne suis pas familier avec ce style d'analyse de complexité «adversaire». L'adversaire modifie-t-il réellement le tableau de manière dynamique pendant que vous recherchez, ou s'agit-il simplement d'un nom donné à la malchance que vous rencontrez dans le pire des cas?
The111
2
@ The111 La malchance équivaut à quelqu'un qui choisit un mauvais chemin qui ne viole pas les choses vues jusqu'à présent, donc ces deux définitions fonctionnent de la même manière. En fait, j'ai du mal à trouver des liens expliquant la technique spécifiquement en ce qui concerne la complexité de calcul ... Je pensais que c'était une idée beaucoup plus connue.
Craig Gidney
Puisque log (1) = 0, l'estimation de la complexité doit être donnée comme O(b*(lg(t)+1))plutôt que O(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».
hardmath
@hardmath je le mentionne dans la réponse. Je l'ai un peu clarifié.
Craig Gidney
17

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:

bool numberSearch(int[][] arr, int value, int minX, int maxX, int minY, int maxY)

if (minX == maxX and minY == maxY and arr[minX,minY] != value)
    return false
if (arr[minX,minY] > value) return false;  // Early exits if the value can't be in 
if (arr[maxX,maxY] < value) return false;  // this subrange at all.
int nextX = (minX + maxX) / 2
int nextY = (minY + maxY) / 2
if (arr[nextX,nextY] == value)
{
    print nextX,nextY
    return true
}
else if (arr[nextX,nextY] < value)
{
    if (numberSearch(arr, value, minX, maxX, nextY + 1, maxY))
        return true
    return numberSearch(arr, value, nextX + 1, maxX, minY, nextY)
}
else
{
    if (numberSearch(arr, value, minX, nextX - 1, minY, maxY))
        return true
    reutrn numberSearch(arr, value, nextX, maxX, minY, nextY)
}
Jeffrey L Whitledge
la source
+1: Il s'agit d'une stratégie O (log (N)), et donc d'un ordre aussi bon qu'on va l'obtenir.
Rex Kerr
3
@Rex Kerr - Cela ressemble à O (log (N)), puisque c'est ce qu'est une recherche binaire normale, cependant, notez qu'il y a potentiellement deux appels récursifs à chaque niveau. Cela signifie que c'est bien pire qu'une simple logarithmique. Je ne pense pas que le pire des cas soit meilleur que O (M + N) car, potentiellement, chaque ligne ou chaque colonne doit être recherchée. Je suppose que cet algorithme pourrait battre le pire des cas pour beaucoup de valeurs, cependant. Et la meilleure partie est que c'est paralellisable, car c'est là que le matériel se dirige ces derniers temps.
Jeffrey L Whitledge
1
@JLW: C'est O (log (N)) - mais c'est en fait O (log_ (4/3) (N ^ 2)) ou quelque chose comme ça. Voir la réponse de Svante ci-dessous. Votre réponse est en fait la même (si vous vouliez dire récursif comme je pense que vous l'avez fait).
Rex Kerr
1
@Svante - Les sous-tableaux ne se chevauchent pas. Dans la première option, ils n'ont pas d'élément y en commun. Dans la deuxième option, ils n'ont pas d'élément x en commun.
Jeffrey L Whitledge
1
Je ne sais pas si c'est logarithmique. J'ai calculé la complexité en utilisant la relation de récurrence approximative T (0) = 1, T (A) = T (A / 2) + T (A / 4) + 1, où A est la zone de recherche, et j'ai fini avec T ( A) = O (Fib (lg (A))), qui est approximativement O (A ^ 0,7) et pire que O (n + m) qui est O (A ^ 0,5). Peut-être ai-je fait une erreur stupide, mais il semble que l'algorithme perd beaucoup de temps à descendre des branches infructueuses.
Craig Gidney
6

Les deux principales réponses données jusqu'à présent semblent être la O(log N)"méthode ZigZag" et la O(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

entrez la description de l'image ici

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:

public class SearchSortedArray2D {

    static boolean findZigZag(int[][] a, int t) {
        int i = 0;
        int j = a.length - 1;
        while (i <= a.length - 1 && j >= 0) {
            if (a[i][j] == t) return true;
            else if (a[i][j] < t) i++;
            else j--;
        }
        return false;
    }

    static boolean findBinarySearch(int[][] a, int t) {
        return findBinarySearch(a, t, 0, 0, a.length - 1, a.length - 1);
    }

    static boolean findBinarySearch(int[][] a, int t,
            int r1, int c1, int r2, int c2) {
        if (r1 > r2 || c1 > c2) return false; 
        if (r1 == r2 && c1 == c2 && a[r1][c1] != t) return false;
        if (a[r1][c1] > t) return false;
        if (a[r2][c2] < t) return false;

        int rm = (r1 + r2) / 2;
        int cm = (c1 + c2) / 2;
        if (a[rm][cm] == t) return true;
        else if (a[rm][cm] > t) {
            boolean b1 = findBinarySearch(a, t, r1, c1, r2, cm - 1);
            boolean b2 = findBinarySearch(a, t, r1, cm, rm - 1, c2);
            return (b1 || b2);
        } else {
            boolean b1 = findBinarySearch(a, t, r1, cm + 1, rm, c2);
            boolean b2 = findBinarySearch(a, t, rm + 1, c1, r2, c2);
            return (b1 || b2);
        }
    }

    static void randomizeArray(int[][] a, int N) {
        int ri = (int) (Math.random() * N);
        int rj = (int) (Math.random() * N);
        a[ri][rj] = 2;
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                if (i == ri && j == rj) continue;
                else if (i > ri || j > rj) a[i][j] = 3;
                else a[i][j] = 1;
            }
        }
    }

    public static void main(String[] args) {

        int N = 8000;
        int[][] a = new int[N][N];
        int randoms = 100;
        int repeats = 1000;

        long start, end, duration;
        long zigMin = Integer.MAX_VALUE, zigMax = Integer.MIN_VALUE;
        long binMin = Integer.MAX_VALUE, binMax = Integer.MIN_VALUE;
        long zigSum = 0, zigAvg;
        long binSum = 0, binAvg;

        for (int k = 0; k < randoms; k++) {
            randomizeArray(a, N);

            start = System.currentTimeMillis();
            for (int i = 0; i < repeats; i++) findZigZag(a, 2);
            end = System.currentTimeMillis();
            duration = end - start;
            zigSum += duration;
            zigMin = Math.min(zigMin, duration);
            zigMax = Math.max(zigMax, duration);

            start = System.currentTimeMillis();
            for (int i = 0; i < repeats; i++) findBinarySearch(a, 2);
            end = System.currentTimeMillis();
            duration = end - start;
            binSum += duration;
            binMin = Math.min(binMin, duration);
            binMax = Math.max(binMax, duration);
        }
        zigAvg = zigSum / randoms;
        binAvg = binSum / randoms;

        System.out.println(findZigZag(a, 2) ?
                "Found via zigzag method. " : "ERROR. ");
        //System.out.println("min search time: " + zigMin + "ms");
        System.out.println("max search time: " + zigMax + "ms");
        System.out.println("avg search time: " + zigAvg + "ms");

        System.out.println();

        System.out.println(findBinarySearch(a, 2) ?
                "Found via binary search method. " : "ERROR. ");
        //System.out.println("min search time: " + binMin + "ms");
        System.out.println("max search time: " + binMax + "ms");
        System.out.println("avg search time: " + binAvg + "ms");
    }
}
Le111
la source
1
+1 Yay, données. :) Il pourrait aussi être intéressant de voir comment ces deux approches se comportent sur les tableaux NxM, puisque la recherche binaire semble devoir devenir intuitivement plus utile à mesure que l'on aborde un cas unidimensionnel.
Nate Kohl
5

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.

1 2 3 4 *
2 3 4 * 7
3 4 * 7 8
4 * 7 8 9
* 7 8 9 10

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é.

Rafał Dowgird
la source
Vous n'avez pas démontré que cette réponse est optimale. Prenons, par exemple, un tableau de dix en travers et d'un million en bas dans lequel la cinquième ligne contient des valeurs toutes supérieures à la valeur cible. Dans ce cas, l'algorithme proposé effectuera une recherche linéaire jusqu'à 999 995 valeurs avant de se rapprocher de la cible. Un algorithme bifurquant comme le mien ne recherchera que 18 valeurs avant de se rapprocher de la cible. Et il fonctionne (asymtotiquement) pas pire que l'algorithme proposé dans tous les autres cas.
Jeffrey L Whitledge
@Jeffrey: C'est une borne inférieure du problème pour le cas pessimiste. Vous pouvez optimiser pour de bonnes entrées, mais il existe des entrées où vous ne pouvez pas faire mieux que linéaire.
Rafał Dowgird
Oui, il existe des entrées pour lesquelles vous ne pouvez pas faire mieux que linéaire. Dans ce cas, mon algorithme effectue cette recherche linéaire. Mais il existe d'autres entrées où vous pouvez faire bien mieux que linéaire. Ainsi la solution proposée n'est pas optimale, puisqu'elle effectue toujours une recherche linéaire.
Jeffrey L Whitledge
Cela montre que l'algorithme doit prendre BigOmega (min (n, m)) temps, pas BigOmega (n + m). C'est pourquoi vous pouvez faire beaucoup mieux lorsqu'une dimension est nettement plus petite. Par exemple, si vous savez qu'il n'y aura qu'une seule ligne, vous pouvez résoudre le problème en temps logarithmique. Je pense qu'un algorithme optimal prendra le temps O (min (n + m, n lg m, m lg n)).
Craig Gidney
Mise à jour de la réponse en conséquence.
Rafał Dowgird
4

Je pense que voici la réponse et cela fonctionne pour tout type de matrice triée

bool findNum(int arr[][ARR_MAX],int xmin, int xmax, int ymin,int ymax,int key)
{
    if (xmin > xmax || ymin > ymax || xmax < xmin || ymax < ymin) return false;
    if ((xmin == xmax) && (ymin == ymax) && (arr[xmin][ymin] != key)) return false;
    if (arr[xmin][ymin] > key || arr[xmax][ymax] < key) return false;
    if (arr[xmin][ymin] == key || arr[xmax][ymax] == key) return true;

    int xnew = (xmin + xmax)/2;
    int ynew = (ymin + ymax)/2;

    if (arr[xnew][ynew] == key) return true;
    if (arr[xnew][ynew] < key)
    {
        if (findNum(arr,xnew+1,xmax,ymin,ymax,key))
            return true;
        return (findNum(arr,xmin,xmax,ynew+1,ymax,key));
    } else {
        if (findNum(arr,xmin,xnew-1,ymin,ymax,key))
            return true;
        return (findNum(arr,xmin,xmax,ymin,ynew-1,key));
    }
}
Sridhar Ravipati
la source
1

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.

Grembo
la source
Un vote défavorable sans commentaire? Je pense que c'est O (N ^ 1/2) car les pires performances nécessitent une vérification de la diagonale. Au moins, montrez-moi un contre-exemple où cette méthode ne fonctionne pas!
Grembo
+1: belle solution ... créative, et bien qu'elle trouve toutes les solutions.
Tony Delroy
1

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.

Nikhil KR
la source
0

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é

Tuomas Pelkonen
la source
0

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

Woot4Moo
la source
0

Eh bien, pour commencer, supposons que nous utilisons un carré.

1 2 3
2 3 4
3 4 5

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 localiser 5à (2,2).

Ensuite, je suis assuré que si 4est dans le tableau, il est à une position soit (x,2)ou (2,x)avec xdans[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 Net Mdifférer (avec un rectangle), considérez ce cas dégénéré:

1  2  3  4  5  6  7  8
2  3  4  5  6  7  8  9
10 11 12 13 14 15 16 17

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 si 9est dans le tableau, c'est soit dans la sous-partie:

            5  6  7  8
            6  7  8  9
10 11 12 13 14 15 16

Cela nous donne 2 rectangles:

5 6 7 8    10 11 12 13 14 15 16
6 7 8 9

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:

  • Appliquer la recherche binaire sur 10 11 12 13 14 15 16, non trouvé
  • Appliquer la recherche binaire sur 5 6 7 8, non trouvé
  • Appliquer la recherche binaire sur 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.

1  2  3  4  5  6  7  8
2  3  4  5  6  7  8  9
10 11 12 13 14 15 16 17
17 .  .  .  .  .  .  17
.                    .
.                    .
.                    .
17 .  .  .  .  .  .  17

Nous avons maintenant un carré.

Bien sûr, nous ne créerons probablement PAS réellement ces lignes, nous pourrions simplement les émuler.

def get(x,y):
  if x < N and y < M: return table[x][y]
  else: return table[N-1][M-1]            # the max

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)

Matthieu M.
la source
0

É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.

Hugh Brackett
la source
Oui, pourquoi le rendre plus complexe qu'il ne doit l'être.
erikkallen
Le tableau n'est pas trié, donc aucune recherche de casier ne peut y être appliquée
Miollnyr
1
Cela ne fonctionnera que si le dernier élément de chaque ligne est plus haut que le premier élément de la ligne suivante, ce qui est une exigence beaucoup plus restrictive que ce que le problème propose.
Jeffrey L Whitledge
Merci, j'ai modifié ma réponse. Je n'ai pas lu assez attentivement, en particulier l'exemple de tableau.
Hugh Brackett
0

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.

Pervez Alam
la source
0

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:

  1. Montez dans la colonne et recherchez l'élément donné jusqu'à la fin. Si trouvé, retourne trouvé comme vrai
  2. Déplacez-vous à gauche dans la ligne et recherchez l'élément donné jusqu'à ce que nous atteignions la fin. Si trouvé, retourne trouvé comme vrai
  3. retour trouvé faux

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.

  • Répétez les étapes ci-dessous jusqu'à ik> = 0
    1. Recherche si a [ik] [j] est égal à l'élément donné. si oui, retourne trouvé comme vrai.
    2. Rechercher si un [i] [jk] est égal à l'élément donné. si oui, retourne trouvé comme vrai.
    3. Incrément k

1 2 4 5 6
2 3 5 7 8
4 6 8 9 10
5 8 9 10 11

Murali Mohan
la source
0
public boolean searchSortedMatrix(int arr[][] , int key , int minX , int maxX , int minY , int maxY){

    // base case for recursion
    if(minX > maxX || minY > maxY)
        return false ;
    // early fails
    // array not properly intialized
    if(arr==null || arr.length==0)
        return false ;
    // arr[0][0]> key return false
    if(arr[minX][minY]>key)
        return false ;
    // arr[maxX][maxY]<key return false
    if(arr[maxX][maxY]<key)
        return false ;
    //int temp1 = minX ;
    //int temp2 = minY ;
    int midX = (minX+maxX)/2 ;
    //if(temp1==midX){midX+=1 ;}
    int midY = (minY+maxY)/2 ;
    //if(temp2==midY){midY+=1 ;}


    // arr[midX][midY] = key ? then value found
    if(arr[midX][midY] == key)
        return true ;
    // alas ! i have to keep looking

    // arr[midX][midY] < key ? search right quad and bottom matrix ;
    if(arr[midX][midY] < key){
        if( searchSortedMatrix(arr ,key , minX,maxX , midY+1 , maxY))
            return true ;
        // search bottom half of matrix
        if( searchSortedMatrix(arr ,key , midX+1,maxX , minY , maxY))
            return true ;
    }
    // arr[midX][midY] > key ? search left quad matrix ;
    else {
         return(searchSortedMatrix(arr , key , minX,midX-1,minY,midY-1));
    }
    return false ;

}
gsb
la source
0

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) et column = (index%total_columns -1)

Cela entraînera uniquement le temps de recherche binaire dans une liste.

Veuillez suggérer des corrections. :)

Abhi31jeet
la source
0

Si la solution O (M log (N)) est correcte pour un tableau MxN -

template <size_t n>
struct MN * get(int a[][n], int k, int M, int N){
  struct MN *result = new MN;
  result->m = -1;
  result->n = -1;

  /* Do a binary search on each row since rows (and columns too) are sorted. */
  for(int i = 0; i < M; i++){
    int lo = 0; int hi = N - 1;
    while(lo <= hi){
      int mid = lo + (hi-lo)/2;
      if(k < a[i][mid]) hi = mid - 1;
      else if (k > a[i][mid]) lo = mid + 1;
      else{
        result->m = i;
        result->n = mid;
        return result;
      }
    }
  }
  return result;
}

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.

Kaushal
la source
0

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é:

  1. Recherche binaire la diagonale du milieu, qui est la diagonale descendant et droite, contenant l'élément à (rows.count/2, columns.count/2).

  2. Si le numéro cible est trouvé, renvoie vrai.

  3. Sinon, deux nombres ( uet v) auront été trouvés tels qu'ils usont plus petits que la cible, vplus grands que la cible, et vun à droite et un en bas de u.

  4. Rechercher récursivement la sous-matrice à droite uet en haut de vet celle en bas uet à gauche de v.

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):

import Cocoa

class Solution {
    func searchMatrix(_ matrix: [[Int]], _ target: Int) -> Bool {
        if (matrix.isEmpty || matrix[0].isEmpty) {
            return false
        }

        return _searchMatrix(matrix, 0..<matrix.count, 0..<matrix[0].count, target)
    }

    func _searchMatrix(_ matrix: [[Int]], _ rows: Range<Int>, _ columns: Range<Int>, _ target: Int) -> Bool {
        if (rows.count == 0 || columns.count == 0) {
            return false
        }
        if (rows.count == 1) {
            return _binarySearch(matrix, rows.lowerBound, columns, target, true)
        }
        if (columns.count == 1) {
            return _binarySearch(matrix, columns.lowerBound, rows, target, false)
        }

        var lowerInflection = (-1, -1)
        var upperInflection = (Int.max, Int.max)
        var currentRows = rows
        var currentColumns = columns
        while (currentRows.count > 0 && currentColumns.count > 0 && upperInflection.0 > lowerInflection.0+1) {
            let rowMidpoint = (currentRows.upperBound + currentRows.lowerBound) / 2
            let columnMidpoint = (currentColumns.upperBound + currentColumns.lowerBound) / 2
            let value = matrix[rowMidpoint][columnMidpoint]
            if (value == target) {
                return true
            }

            if (value > target) {
                upperInflection = (rowMidpoint, columnMidpoint)
                currentRows = currentRows.lowerBound..<rowMidpoint
                currentColumns = currentColumns.lowerBound..<columnMidpoint
            } else {
                lowerInflection = (rowMidpoint, columnMidpoint)
                currentRows = rowMidpoint+1..<currentRows.upperBound
                currentColumns = columnMidpoint+1..<currentColumns.upperBound
            }
        }
        if (lowerInflection.0 == -1) {
            lowerInflection = (upperInflection.0-1, upperInflection.1-1)
        } else if (upperInflection.0 == Int.max) {
            upperInflection = (lowerInflection.0+1, lowerInflection.1+1)
        }

        return _searchMatrix(matrix, rows.lowerBound..<lowerInflection.0+1, upperInflection.1..<columns.upperBound, target) || _searchMatrix(matrix, upperInflection.0..<rows.upperBound, columns.lowerBound..<lowerInflection.1+1, target)
    }

    func _binarySearch(_ matrix: [[Int]], _ rowOrColumn: Int, _ range: Range<Int>, _ target: Int, _ searchRow : Bool) -> Bool {
        if (range.isEmpty) {
            return false
        }

        let midpoint = (range.upperBound + range.lowerBound) / 2
        let value = (searchRow ? matrix[rowOrColumn][midpoint] : matrix[midpoint][rowOrColumn])
        if (value == target) {
            return true
        }

        if (value > target) {
            return _binarySearch(matrix, rowOrColumn, range.lowerBound..<midpoint, target, searchRow)
        } else {
            return _binarySearch(matrix, rowOrColumn, midpoint+1..<range.upperBound, target, searchRow)
        }
    }
}
digbybare
la source
-1

Étant donné une matrice carrée comme suit:

[abc]
[def]
[ijk]

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,

1) Vérifiez la ligne 1. j <c? (non, allez ensuite)

2) Vérifiez la ligne 2. j <f? (oui, la recherche de bac n'obtient rien)

3) Vérifiez la ligne 3. j <k? (oui, la recherche de bac le trouve)

Réessayez avec N = q,

1) Vérifiez la ligne 1. q <c? (non, allez ensuite)

2) Vérifiez la ligne 2. q <f? (non, allez ensuite)

3) Vérifiez la ligne 3. q <k? (non, allez ensuite)

Il existe probablement une meilleure solution, mais c'est facile à expliquer .. :)

apandit
la source