Come cerco un numero in un array 2d ordinato da sinistra a destra e dall'alto in basso?


90

Di recente mi è stata posta questa domanda per l'intervista e sono curioso di sapere quale sarebbe una buona soluzione.

Supponiamo che mi venga fornito un array 2d in cui tutti i numeri nell'array sono in ordine crescente da sinistra a destra e dall'alto verso il basso.

Qual è il modo migliore per cercare e determinare se un numero di destinazione è nella matrice?

Ora, la mia prima inclinazione è quella di utilizzare una ricerca binaria poiché i miei dati sono ordinati. Posso determinare se un numero è in una singola riga nel tempo O (log N). Tuttavia, sono le 2 direzioni che mi sbalordiscono.

Un'altra soluzione che ho pensato potrebbe funzionare è iniziare da qualche parte nel mezzo. Se il valore medio è inferiore al mio obiettivo, posso essere certo che si trova nella parte quadrata sinistra della matrice dal centro. Quindi mi sposto in diagonale e controllo di nuovo, riducendo le dimensioni del quadrato in cui potrebbe trovarsi il bersaglio finché non ho affinato il numero di destinazione.

Qualcuno ha delle buone idee per risolvere questo problema?

Matrice di esempio:

Ordinati da sinistra a destra, dall'alto in basso.

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

Domanda semplice: può essere che tu possa avere un vicino con lo stesso valore [[1 1][1 1]]:?
Matthieu M.

Risposte:


115

Ecco un approccio semplice:

  1. Inizia dall'angolo in basso a sinistra.
  2. Se l'obiettivo è inferiore a quel valore, deve essere sopra di noi, quindi sali di uno .
  3. Altrimenti sappiamo che l'obiettivo non può essere in quella colonna, quindi spostati a destra .
  4. Vai a 2.

Per un NxMarray, questo viene eseguito O(N+M). Penso che sarebbe difficile fare di meglio. :)


Modifica: molte buone discussioni. Stavo parlando del caso generale sopra; chiaramente, se No M sono piccoli, potresti usare un approccio di ricerca binaria per farlo in qualcosa che si avvicina al tempo logaritmico.

Ecco alcuni dettagli, per chi è curioso:

Storia

Questo semplice algoritmo è chiamato Saddleback Search . È in circolazione da un po 'ed è ottimale quando N == M. Alcuni riferimenti:

Tuttavia, quando N < M, l'intuizione suggerisce che la ricerca binaria dovrebbe essere in grado di fare di meglio di O(N+M): Ad esempio, quando N == 1, una ricerca binaria pura verrà eseguita in tempo logaritmico anziché lineare.

Peggior caso vincolato

Richard Bird ha esaminato questa intuizione che la ricerca binaria potrebbe migliorare l'algoritmo Saddleback in un documento del 2006:

Utilizzando una tecnica di conversazione piuttosto insolita, Bird ci mostra che N <= Mquesto problema ha un limite inferiore di Ω(N * log(M/N)). Questo limite ha senso, in quanto ci fornisce prestazioni lineari quando N == Me prestazioni logaritmiche quando N == 1.

Algoritmi per array rettangolari

Un approccio che utilizza una ricerca binaria riga per riga è simile a questo:

  1. Inizia con una matrice rettangolare dove N < M. Diciamo che Nsono righe e Mcolonne.
  2. Esegui una ricerca binaria nella riga centrale di value. Se lo troviamo, abbiamo finito.
  3. Altrimenti abbiamo trovato una coppia di numeri adiacenti se g, dove s < value < g.
  4. Il rettangolo di numeri sopra ea sinistra di sè minore di value, quindi possiamo eliminarlo.
  5. Il rettangolo in basso ea destra di gè maggiore di value, quindi possiamo eliminarlo.
  6. Vai al passaggio (2) per ciascuno dei due rettangoli rimanenti.

In termini di complessità nel caso peggiore, questo algoritmo log(M)funziona per eliminare metà delle possibili soluzioni, quindi si chiama ricorsivamente due volte su due problemi più piccoli. Dobbiamo ripetere una versione più piccola di quel log(M)lavoro per ogni riga, ma se il numero di righe è piccolo rispetto al numero di colonne, allora la possibilità di eliminare tutte quelle colonne in tempo logaritmico inizia a diventare utile .

Ciò conferisce all'algoritmo una complessità di T(N,M) = log(M) + 2 * T(M/2, N/2)cui Bird mostra essere O(N * log(M/N)).

Un altro approccio pubblicato da Craig Gidney descrive un algoritmo simile all'approccio precedente: esamina una riga alla volta utilizzando una dimensione del passo di M/N. La sua analisi mostra che questo si traduce anche in O(N * log(M/N))prestazioni.

Confronto delle prestazioni

L'analisi Big-O va benissimo, ma quanto funzionano questi approcci nella pratica? Il grafico seguente esamina quattro algoritmi per array sempre più "quadrati":

prestazioni dell'algoritmo vs ortogonalità

(L'algoritmo "ingenuo" cerca semplicemente ogni elemento dell'array. L'algoritmo "ricorsivo" è descritto sopra. L'algoritmo "ibrido" è un'implementazione dell'algoritmo di Gidney . Per ogni dimensione dell'array, le prestazioni sono state misurate temporizzando ogni algoritmo su un insieme fisso di 1.000.000 di array generati casualmente.)

Alcuni punti notevoli:

  • Come previsto, gli algoritmi di "ricerca binaria" offrono le migliori prestazioni su array rettangolari e l'algoritmo Saddleback funziona al meglio su array quadrati.
  • L'algoritmo Saddleback ha prestazioni peggiori dell'algoritmo "ingenuo" per array 1-d, presumibilmente perché esegue confronti multipli su ogni elemento.
  • Il calo di prestazioni che gli algoritmi di "ricerca binaria" assumono sugli array quadrati è presumibilmente dovuto al sovraccarico dell'esecuzione di ricerche binarie ripetute.

Sommario

Un uso intelligente della ricerca binaria può fornire O(N * log(M/N)prestazioni sia per array rettangolari che quadrati. L' O(N + M)algoritmo "saddleback" è molto più semplice, ma risente del degrado delle prestazioni quando gli array diventano sempre più rettangolari.


6
applica la ricerca binaria alla passeggiata diagonale e ottieni O (logN) o O (logM) a seconda di quale sia il valore più alto.
Anurag

3
@Anurag - Non penso che la complessità funzioni così bene. Una ricerca binaria ti darà un buon punto di partenza, ma dovrai percorrere una dimensione o l'altra fino in fondo e, nel peggiore dei casi, potresti comunque iniziare da un angolo e finire nell'altro.
Jeffrey L Whitledge

1
Se N = 1 e M = 1000000 posso fare meglio di O (N + M), quindi un'altra soluzione è applicare la ricerca binaria in ogni riga che porta O (N * log (M)) dove N <M nel caso in cui questo restituisca costante minore.
Luka Rahne

1
Ho fatto alcuni test utilizzando sia il tuo metodo che il metodo di ricerca binaria e ho pubblicato i risultati QUI . Sembra che il metodo a zigzag sia il migliore, a meno che non sia riuscito a generare correttamente le condizioni peggiori per entrambi i metodi.
The111

1
Buon uso dei riferimenti! Tuttavia, quando M==Nvogliamo la O(N)complessità, non O(N*log(N/N))poiché quest'ultima è zero. Un limite netto "unificato" corretto è O(N*(log(M/N)+1))quando N<=M.
hardmath

35

Questo problema richiede Θ(b lg(t))tempo, dove b = min(w,h)e t=b/max(w,h). Discuto la soluzione in questo post del blog .

Limite inferiore

Un avversario può forzare un algoritmo a fare delle Ω(b lg(t))query, limitandosi alla diagonale principale:

Avversario che usa la diagonale principale

Legenda: le celle bianche sono elementi più piccoli, le celle grigie sono elementi più grandi, le celle gialle sono elementi più piccoli o uguali e le celle arancioni sono elementi più grandi o uguali. L'avversario forza la soluzione a qualsiasi cella gialla o arancione che l'algoritmo interroga per ultima.

Si noti che esistono belenchi di dimensioni ordinati indipendenti t, che richiedono l' Ω(b lg(t))eliminazione completa delle query.

Algoritmo

  1. (Assumere senza perdita di generalità che w >= h)
  2. Confronta l'elemento di destinazione con la cella ta sinistra dell'angolo superiore destro dell'area valida
    • Se l'elemento della cella corrisponde, restituisce la posizione corrente.
    • Se l'elemento della cella è inferiore all'elemento di destinazione, eliminare le tcelle rimanenti nella riga con una ricerca binaria. Se durante l'operazione viene trovato un elemento corrispondente, torna con la sua posizione.
    • In caso contrario, l'elemento della cella è maggiore dell'elemento di destinazione, eliminando le tcolonne brevi.
  3. Se non è rimasta un'area valida, restituisci l'errore
  4. Vai al passaggio 2

Trovare un oggetto:

Trovare un oggetto

Determinare un elemento non esiste:

Determinare un oggetto non esiste

Legenda: le celle bianche sono oggetti più piccoli, le celle grigie sono oggetti più grandi e la cella verde è un oggetto uguale.

Analisi

Ci sono b*tbrevi colonne da eliminare. Ci sono blunghe file da eliminare. Eliminare una lunga fila costa O(lg(t))tempo. L'eliminazione di tcolonne corte costa O(1)tempo.

Nel peggiore dei casi dovremo eliminare ogni colonna e ogni riga, richiedendo tempo O(lg(t)*b + b*t*1/t) = O(b lg(t)).

Nota che sto assumendo che i lgmorsetti abbiano un risultato superiore a 1 (cioè lg(x) = log_2(max(2,x))). Ecco perché quando w=h, nel senso t=1, otteniamo il limite atteso di O(b lg(1)) = O(b) = O(w+h).

Codice

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

1
Interessante e forse parzialmente sopra la mia testa. Non ho familiarità con questo stile "avversario" di analisi della complessità. L'avversario sta effettivamente in qualche modo cambiando dinamicamente l'array durante la ricerca, o è solo un nome dato alla sfortuna che incontri nella ricerca del caso peggiore?
111

2
@ The111 La sfortuna equivale a qualcuno che sceglie un sentiero sbagliato che non viola le cose viste finora, quindi entrambe le definizioni funzionano allo stesso modo. In realtà ho problemi a trovare collegamenti che spieghino la tecnica in modo specifico rispetto alla complessità computazionale ... Pensavo che fosse un'idea molto più nota.
Craig Gidney

Poiché log (1) = 0, la stima della complessità dovrebbe essere data come O(b*(lg(t)+1))invece di O(b*lg(t)). Bella recensione, specialmente. per aver richiamato l'attenzione sulla "tecnica dell'avversario" nel mostrare un limite "caso peggiore".
hardmath

@hardmath lo menziono nella risposta. L'ho chiarito un po '.
Craig Gidney

17

Per questo problema utilizzerei la strategia divide et impera, simile a quanto suggerito, ma i dettagli sono leggermente diversi.

Questa sarà una ricerca ricorsiva sui sottointervalli della matrice.

Ad ogni passaggio, scegli un elemento al centro dell'intervallo. Se il valore trovato è quello che stai cercando, il gioco è fatto.

Altrimenti, se il valore trovato è inferiore al valore che stai cercando, allora sai che non si trova nel quadrante sopra ea sinistra della tua posizione attuale. Quindi cerca ricorsivamente i due sottointervalli: tutto (esclusivamente) sotto la posizione corrente e tutto (esclusivamente) a destra che si trova nella o sopra la posizione corrente.

Altrimenti, (il valore trovato è maggiore del valore che stai cercando) sai che non si trova nel quadrante sottostante ea destra della tua posizione attuale. Quindi cerca ricorsivamente i due sottointervalli: tutto (esclusivamente) a sinistra della posizione corrente e tutto (esclusivamente) sopra la posizione corrente che si trova sulla colonna corrente o una colonna a destra.

E ba-da-bing, l'hai trovato.

Si noti che ogni chiamata ricorsiva si occupa solo del sottointervallo corrente, non (ad esempio) TUTTE le righe sopra la posizione corrente. Solo quelli nel sottogruppo corrente.

Ecco alcuni pseudocodici per te:

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

+1: Questa è una strategia O (log (N)), e quindi è un buon ordine che si otterrà.
Rex Kerr

3
@Rex Kerr - Sembra O (log (N)), poiché è ciò che è una normale ricerca binaria, tuttavia, nota che ci sono potenzialmente due chiamate ricorsive a ciascun livello. Ciò significa che è molto peggio del semplice logaritmico. Non credo che il caso peggiore sia migliore di O (M + N) poiché, potenzialmente, ogni riga o ogni colonna deve essere cercata. Immagino che questo algoritmo possa battere il caso peggiore per molti valori, però. E la parte migliore è che è paralellizzabile, poiché è lì che è diretto l'hardware ultimamente.
Jeffrey L Whitledge

1
@JLW: è O (log (N)) - ma in realtà è O (log_ (4/3) (N ^ 2)) o qualcosa del genere. Vedi la risposta di Svante di seguito. La tua risposta è in realtà la stessa (se intendi ricorsivo nel modo in cui penso tu lo abbia fatto).
Rex Kerr

1
@Svante - I sottoarray non si sovrappongono. Nella prima opzione, non hanno elementi y in comune. Nella seconda opzione, non hanno elementi x in comune.
Jeffrey L Whitledge,

1
Non sono sicuro che sia logaritmico. Ho calcolato la complessità utilizzando la relazione di ricorrenza approssimativa T (0) = 1, T (A) = T (A / 2) + T (A / 4) + 1, dove A è l'area di ricerca, e ho finito con T ( A) = O (Fib (lg (A))), che è approssimativamente O (A ^ 0,7) e peggiore di O (n + m) che è O (A ^ 0,5). Forse ho commesso uno stupido errore, ma sembra che l'algoritmo stia sprecando molto tempo andando giù per rami infruttuosi.
Craig Gidney

6

Le due risposte principali fornite finora sembrano essere il O(log N)"metodo ZigZag" e il O(N+M)metodo di ricerca binaria. Ho pensato di fare alcuni test confrontando i due metodi con alcune varie configurazioni. Ecco i dettagli:

L'array è N x N quadrati in ogni test, con N che varia da 125 a 8000 (il più grande che il mio heap JVM potrebbe gestire). Per ogni dimensione dell'array, ho scelto un posto casuale nell'array per metterne un singolo 2. Ho quindi inserito 3ovunque possibile (a destra e sotto il 2) e poi ho riempito il resto della matrice con1. Alcuni dei commentatori precedenti sembravano pensare che questo tipo di configurazione avrebbe prodotto il tempo di esecuzione nel caso peggiore per entrambi gli algoritmi. Per ogni dimensione dell'array, ho scelto 100 diverse posizioni casuali per il 2 (obiettivo di ricerca) e ho eseguito il test. Ho registrato il tempo di esecuzione medio e il tempo di esecuzione nel caso peggiore per ogni algoritmo. Poiché stava accadendo troppo velocemente per ottenere buone letture di ms in Java, e poiché non mi fido di nanoTime () di Java, ho ripetuto ogni test 1000 volte solo per aggiungere un fattore di bias uniforme a tutte le volte. Ecco i risultati:

inserisci qui la descrizione dell'immagine

ZigZag ha battuto il binario in ogni test sia per i tempi medi che per i tempi peggiori, tuttavia, sono tutti entro un ordine di grandezza l'uno dall'altro più o meno.

Ecco il codice 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");
    }
}

1
+1 Sì, dati. :) Potrebbe anche essere interessante vedere come si comportano questi due approcci sugli array NxM, poiché la ricerca binaria sembra che dovrebbe intuitivamente diventare più utile man mano che ci avviciniamo a un caso unidimensionale.
Nate Kohl

5

Questa è una breve dimostrazione del limite inferiore del problema.

Non puoi farlo meglio del tempo lineare (in termini di dimensioni dell'array, non del numero di elementi). Nella matrice sottostante, ciascuno degli elementi contrassegnati come *può essere 5 o 6 (indipendentemente dagli altri). Quindi, se il valore target è 6 (o 5), l'algoritmo deve esaminarli tutti.

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

Ovviamente questo si estende anche ad array più grandi. Ciò significa che questa risposta è ottimale.

Aggiornamento: come sottolineato da Jeffrey L Whitledge, è ottimale solo come limite inferiore asintotico sul tempo di esecuzione rispetto alla dimensione dei dati di input (trattati come una singola variabile). Il tempo di esecuzione trattato come una funzione a due variabili su entrambe le dimensioni dell'array può essere migliorato.


Non hai dimostrato che quella risposta sia ottimale. Si consideri, ad esempio, un array di dieci e un milione in meno in cui la quinta riga contiene valori tutti superiori al valore di destinazione. In tal caso, l'algoritmo proposto eseguirà una ricerca più lineare fino a 999.995 valori prima di avvicinarsi all'obiettivo. Un algoritmo biforcuto come il mio cercherà solo 18 valori prima di avvicinarsi all'obiettivo. E funziona (asintoticamente) non peggio dell'algoritmo proposto in tutti gli altri casi.
Jeffrey L Whitledge

@ Jeffrey: è un limite inferiore al problema per il caso pessimistico. Puoi ottimizzare per buoni input, ma esistono input in cui non puoi fare meglio del lineare.
Rafał Dowgird

Sì, esistono input in cui non puoi fare meglio del lineare. In tal caso il mio algoritmo esegue quella ricerca lineare. Ma ci sono altri input in cui puoi fare molto meglio del lineare. Quindi la soluzione proposta non è ottimale, poiché esegue sempre una ricerca lineare.
Jeffrey L Whitledge

Questo mostra che l'algoritmo deve prendere BigOmega (tempo min (n, m)), non BigOmega (n + m). Ecco perché puoi fare molto meglio quando una dimensione è significativamente più piccola. Ad esempio, se sai che ci sarà solo 1 riga, puoi risolvere il problema in tempo logaritmico. Penso che un algoritmo ottimale richiederà tempo O (min (n + m, n lg m, m lg n)).
Craig Gidney

Aggiornata la risposta di conseguenza.
Rafał Dowgird

4

Penso che questa sia la risposta e funzioni per qualsiasi tipo di matrice ordinata

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

1

Domanda interessante. Considera questa idea: crea un confine in cui tutti i numeri siano maggiori del tuo obiettivo e un altro in cui tutti i numeri siano inferiori al tuo obiettivo. Se rimane qualcosa tra i due, quello è il tuo obiettivo.

Se cerco 3 nel tuo esempio, leggo tutta la prima riga fino a quando non premo 4, quindi cerco il numero adiacente più piccolo (comprese le diagonali) maggiore di 3:

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

Ora faccio lo stesso per quei numeri inferiori a 3:

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

Ora chiedo, c'è qualcosa dentro i due confini? Se sì, deve essere 3. Se no, allora non c'è 3. Un po 'indiretto poiché in realtà non trovo il numero, deduco solo che deve essere lì. Questo ha il vantaggio di contare TUTTI i 3.

Ho provato questo su alcuni esempi e sembra funzionare bene.


Un voto negativo senza commenti? Penso che questo sia O (N ^ 1/2) poiché la prestazione peggiore richiede un controllo della diagonale. Almeno mostrami un contro esempio in cui questo metodo non funziona!
Grembo

+1: bella soluzione ... creativa, e buona che trovi tutte le soluzioni.
Tony Delroy

1

La ricerca binaria attraverso la diagonale dell'array è l'opzione migliore. Possiamo scoprire se l'elemento è minore o uguale agli elementi nella diagonale.


0

R. Esegui una ricerca binaria sulle righe in cui potrebbe trovarsi il numero di destinazione.

B. Rendilo un grafico: cerca il numero prendendo sempre il più piccolo nodo vicino non visitato e tornando indietro quando viene trovato un numero troppo grande


0

La ricerca binaria sarebbe l'approccio migliore, imo. A partire da 1/2 x, 1/2 y lo taglierà a metà. IE un quadrato 5x5 sarebbe qualcosa come x == 2 / y == 3. Ho arrotondato un valore per difetto e un valore per eccesso in una zona migliore nella direzione del valore targetizzato.

Per chiarezza la prossima iterazione ti darebbe qualcosa come x == 1 / y == 2 OR x == 3 / y == 5


0

Bene, per cominciare, supponiamo di utilizzare un quadrato.

1 2 3
2 3 4
3 4 5

1. Ricerca di una piazza

Userei una ricerca binaria sulla diagonale. L'obiettivo è individuare il numero più piccolo che non è strettamente inferiore al numero di destinazione.

Dire che sto cercando 4per esempio, allora avrei finito per localizzare 5a (2,2).

Quindi, mi è stato assicurato che se 4è nella tabella, è in una posizione (x,2)o (2,x)con xdentro [0,2]. Bene, sono solo 2 ricerche binarie.

La complessità non è scoraggiante: O(log(N))(3 ricerche binarie su intervalli di lunghezza N)

2. Ricerca di un rettangolo, approccio ingenuo

Certo, diventa un po 'più complicato quando Ne Mdifferiscono (con un rettangolo), considera questo caso degenere:

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

E diciamo che sto cercando 9... L'approccio diagonale è ancora buono, ma la definizione di diagonale cambia. Ecco la mia diagonale [1, (5 or 6), 17]. Diciamo che ho raccolto [1,5,17], quindi so che se 9è nella tabella è nella sottoparte:

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

Questo ci dà 2 rettangoli:

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

Quindi possiamo ricorrere! probabilmente cominciando da quello con meno elementi (anche se in questo caso ci uccide).

Tengo a precisare che se una delle dimensioni è minore di 3, non possiamo applicare i metodi diagonali e dobbiamo usare una ricerca binaria. Qui significherebbe:

  • Applica ricerca binaria su 10 11 12 13 14 15 16, non trovato
  • Applica ricerca binaria su 5 6 7 8, non trovato
  • Applica ricerca binaria su 6 7 8 9, non trovato

È complicato perché per ottenere buone prestazioni potresti voler distinguere tra diversi casi, a seconda della forma generale ...

3. Ricerca di un rettangolo, approccio brutale

Sarebbe molto più facile se ci occupassimo di un quadrato ... quindi quadriamo le cose.

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

Ora abbiamo un quadrato.

Naturalmente, probabilmente NON creeremo effettivamente quelle righe, potremmo semplicemente emularle.

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

quindi si comporta come un quadrato senza occupare più memoria (a costo della velocità, probabilmente, dipende dalla cache ... vabbè: p)


0

MODIFICARE:

Ho frainteso la domanda. Come sottolineano i commenti, questo funziona solo nel caso più limitato.

In un linguaggio come C che memorizza i dati in ordine di riga maggiore, trattalo semplicemente come un array 1D di dimensione n * me usa una ricerca binaria.


Sì, perché renderlo più complesso di quanto deve essere.
erikkallen

L'array non è ordinato, quindi non è possibile applicare la ricerca bin
Miollnyr

1
Questo funzionerà solo se l'ultimo elemento di ogni riga è più alto del primo elemento della riga successiva, il che è un requisito molto più restrittivo rispetto a quello proposto dal problema.
Jeffrey L Whitledge

Grazie, ho modificato la mia risposta. Non ho letto abbastanza attentamente, in particolare l'array di esempio.
Hugh Brackett

0

Ho una soluzione Divide & Conquer ricorsiva. L'idea di base per un passaggio è: sappiamo che il Left-Upper (LU) è il più piccolo e il right-bottom (RB) è il numero più grande, quindi il dato No (N) deve: N> = LU e N <= RB

IF N == LU e N == RB :::: Elemento trovato e interrotto che restituisce la posizione / Indice Se N> = LU e N <= RB = FALSE, No non è presente e interrompe. Se N> = LU e N <= RB = TRUE, dividere la matrice 2D in 4 parti uguali di matrice 2D ciascuna in modo logico .. Quindi applicare lo stesso passaggio algoritmico a tutti e quattro i sotto-matrice.

Il mio algoritmo è corretto che ho implementato sul PC dei miei amici. Complessità: ogni 4 confronti può essere usato per dedurre il numero totale di elementi a un quarto nel suo caso peggiore. Quindi la mia complessità arriva ad essere 1 + 4 x lg (n) + 4 Ma mi aspettavo davvero che funzionasse su O (n)

Penso che qualcosa non va da qualche parte nel mio calcolo della complessità, per favore correggi se è così ..


0

La soluzione ottimale è iniziare dall'angolo in alto a sinistra, che ha un valore minimo. Spostati in diagonale verso il basso a destra finché non colpisci un elemento il cui valore> = valore dell'elemento dato. Se il valore dell'elemento è uguale a quello dell'elemento dato, restituisce trovato come vero.

Altrimenti, da qui possiamo procedere in due modi.

Strategia 1:

  1. Spostati in alto nella colonna e cerca l'elemento dato fino a raggiungere la fine. Se trovato, restituisce trovato come vero
  2. Spostati a sinistra nella riga e cerca l'elemento specificato fino a raggiungere la fine. Se trovato, restituisce trovato come vero
  3. ritorno trovato come falso

Strategia 2: Indichiamo con l'indice di riga e j l'indice di colonna dell'elemento diagonale a cui ci siamo fermati. (Qui, abbiamo i = j, BTW). Sia k = 1.

  • Ripeti i passaggi seguenti fino a quando ik> = 0
    1. Cerca se un [ik] [j] è uguale all'elemento dato. se sì, restituisce ritenuto vero.
    2. Cerca se a [i] [jk] è uguale all'elemento dato. se sì, restituisce ritenuto vero.
    3. Incremento k

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


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 ;

}

0

Suggerisco di memorizzare tutti i personaggi in un file 2D list. quindi trova l'indice dell'elemento richiesto se esiste nell'elenco.

Se non presente stampa il messaggio appropriato altrimenti stampa riga e colonna come:

row = (index/total_columns) e column = (index%total_columns -1)

Ciò comporterà solo il tempo di ricerca binaria in un elenco.

Si prega di suggerire eventuali correzioni. :)


0

Se la soluzione O (M log (N)) va bene per un array 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;
}

Demo C ++ funzionante.

Per favore fammi sapere se questo non funziona o se c'è un bug.


0

Ho posto questa domanda nelle interviste per la maggior parte di un decennio e penso che solo una persona sia stata in grado di trovare un algoritmo ottimale.

La mia soluzione è sempre stata:

  1. Ricerca binaria la diagonale centrale, che è la diagonale che scorre verso il basso e verso destra, contenente l'elemento in (rows.count/2, columns.count/2).

  2. Se viene trovato il numero di destinazione, restituisce true.

  3. Altrimenti, saranno stati trovati due numeri ( ue v) tali che uè più piccolo del target, vè più grande del target ed vè uno a destra e uno in basso u.

  4. Cerca in modo ricorsivo la sotto-matrice a destra ue in alto di ve quella in fondo uea sinistra di v.

Credo che questo sia un netto miglioramento rispetto all'algoritmo fornito da Nate qui , poiché la ricerca della diagonale spesso consente una riduzione di oltre la metà dello spazio di ricerca (se la matrice è vicina al quadrato), mentre la ricerca di una riga o di una colonna risulta sempre in un'eliminazione di esattamente la metà.

Ecco il codice in (probabilmente non eccessivamente Swifty) Swift:

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

-1

Data una matrice quadrata come segue:

[abc]
[def]
[ijk]

Sappiamo che a <c, d <f, i <k. Quello che non sappiamo è se d <c o d> c, ecc. Abbiamo garanzie solo in una dimensione.

Guardando gli elementi finali (c, f, k), possiamo fare una sorta di filtro: è N <c? cerca (): successivo (). Quindi, abbiamo n iterazioni sulle righe, con ogni riga che prende O (log (n)) per la ricerca binaria o O (1) se filtrata.

Vorrei fornire un ESEMPIO in cui N = j,

1) Controllare la riga 1. j <c? (no, vai avanti)

2) Controllare la riga 2. j <f? (sì, la ricerca nel cestino non ottiene nulla)

3) Controllare la riga 3. j <k? (sì, bin search lo trova)

Riprova con N = q,

1) Controllare la riga 1. q <c? (no, vai avanti)

2) Controllare la riga 2. q <f? (no, vai avanti)

3) Controllare la riga 3. q <k? (no, vai avanti)

Probabilmente c'è una soluzione migliore là fuori, ma è facile da spiegare .. :)


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.