Como procuro um número em uma matriz 2d classificada da esquerda para a direita e de cima para baixo?

90

Recentemente, recebi esta pergunta da entrevista e estou curioso para saber qual seria uma boa solução para isso.

Digamos que eu receba uma matriz 2d em que todos os números na matriz estão em ordem crescente da esquerda para a direita e de cima para baixo.

Qual é a melhor maneira de pesquisar e determinar se um número de destino está na matriz?

Agora, minha primeira inclinação é utilizar uma pesquisa binária, já que meus dados são classificados. Posso determinar se um número está em uma única linha no tempo O (log N). No entanto, são as 2 direções que me confundem.

Outra solução que acho que pode funcionar é começar em algum lugar no meio. Se o valor do meio for menor que meu objetivo, posso ter certeza de que ele está na parte quadrada esquerda da matriz a partir do meio. Eu então me movo diagonalmente e verifico novamente, reduzindo o tamanho do quadrado em que o alvo poderia estar, até que eu tenha focado no número alvo.

Alguém tem boas idéias para resolver esse problema?

Matriz de exemplo:

Ordenado da esquerda para a direita, de cima para baixo.

1  2  4  5  6  
2  3  5  7  8  
4  6  8  9  10  
5  8  9  10 11  
Phukab
fonte
Pergunta simples: será que você pode ter um vizinho com o mesmo valor [[1 1][1 1]]:?
Matthieu M.

Respostas:

115

Esta é uma abordagem simples:

  1. Comece no canto inferior esquerdo.
  2. Se a meta for menor que esse valor, deve estar acima de nós, então mova um para cima .
  3. Caso contrário, sabemos que o destino não pode estar nessa coluna, então para a direita .
  4. Vá para 2.

Para uma NxMmatriz, isso é executado em O(N+M). Acho que seria difícil fazer melhor. :)


Edit: Muita boa discussão. Eu estava falando sobre o caso geral acima; claramente, se NouM forem pequenos, você pode usar uma abordagem de pesquisa binária para fazer isso em algo próximo ao tempo logarítmico.

Aqui estão alguns detalhes, para quem tem curiosidade:

História

Esse algoritmo simples é chamado de Saddleback Search . Já existe há algum tempo e é o ideal quando N == M. Algumas referências:

No entanto, quando N < M, a intuição sugere que a pesquisa binária deve ser capaz de fazer melhor do que O(N+M): Por exemplo, quandoN == 1 , uma pesquisa binária pura será executada em tempo logarítmico em vez de linear.

Limite de pior caso

Richard Bird examinou essa intuição de que a busca binária poderia melhorar o algoritmo de Saddleback em um artigo de 2006:

Usando uma técnica de conversação bastante incomum, Bird nos mostra que para N <= M, esse problema tem um limite inferior de Ω(N * log(M/N)). Este limite faz sentido, pois nos dá desempenho linear quando N == Me desempenho logarítmico quandoN == 1 .

Algoritmos para matrizes retangulares

Uma abordagem que usa uma pesquisa binária linha por linha é a seguinte:

  1. Comece com uma matriz retangular onde N < M. Digamos que Nsão linhas e Mcolunas.
  2. Faça uma pesquisa binária na linha do meio para value. Se encontrarmos, estamos prontos.
  3. Caso contrário, encontramos um par adjacente de números se g, onde s < value < g.
  4. O retângulo de números acima e à esquerda de sé menor que value, portanto, podemos eliminá-lo.
  5. O retângulo abaixo e à direita de gé maior que value, para que possamos eliminá-lo.
  6. Vá para a etapa (2) para cada um dos dois retângulos restantes.

Em termos de complexidade de pior caso, esse algoritmo log(M)funciona para eliminar metade das soluções possíveis e, em seguida, chama a si mesmo recursivamente duas vezes em dois problemas menores. Precisamos repetir uma versão menor desse log(M)trabalho para cada linha, mas se o número de linhas for pequeno em comparação com o número de colunas, então, poder eliminar todas essas colunas em tempo logarítmico começa a valer a pena .

Isso dá ao algoritmo uma complexidade de T(N,M) = log(M) + 2 * T(M/2, N/2), que Bird mostra ser O(N * log(M/N)).

Outra abordagem postada por Craig Gidney descreve um algoritmo semelhante à abordagem acima: ele examina uma linha por vez usando um tamanho de passo de M/N. Sua análise mostra que isso também resulta em O(N * log(M/N))desempenho.

Comparação de Desempenho

A análise Big-O é muito boa, mas quão bem essas abordagens funcionam na prática? O gráfico abaixo examina quatro algoritmos para matrizes cada vez mais "quadradas":

desempenho do algoritmo vs quadratura

(O algoritmo "ingênuo" simplesmente pesquisa todos os elementos da matriz. O algoritmo "recursivo" é descrito acima. O algoritmo "híbrido" é uma implementação do algoritmo de Gidney . Para cada tamanho de matriz, o desempenho foi medido cronometrando cada algoritmo em um conjunto fixo de 1.000.000 matrizes geradas aleatoriamente.)

Alguns pontos notáveis:

  • Como esperado, os algoritmos de "busca binária" oferecem o melhor desempenho em matrizes retangulares e o algoritmo Saddleback funciona melhor em matrizes quadradas.
  • O algoritmo Saddleback tem desempenho pior do que o algoritmo "ingênuo" para matrizes 1-d, provavelmente porque faz várias comparações em cada item.
  • O impacto no desempenho que os algoritmos de "pesquisa binária" assumem em matrizes quadradas é provavelmente devido à sobrecarga de executar pesquisas binárias repetidas.

Resumo

O uso inteligente da pesquisa binária pode fornecer O(N * log(M/N)desempenho para matrizes retangulares e quadradas. O O(N + M)algoritmo "saddleback" é muito mais simples, mas sofre de degradação de desempenho à medida que os arrays se tornam cada vez mais retangulares.

Nate Kohl
fonte
6
aplique a pesquisa binária à caminhada diagonal e você terá O (logN) ou O (logM), o que for mais alto.
Anurag
3
@Anurag - Não acho que a complexidade funcione muito bem. Uma pesquisa binária será um bom lugar para começar, mas você terá que caminhar por uma dimensão ou outra até o fim e, na pior das hipóteses, você ainda pode começar em um canto e terminar no outro.
Jeffrey L Whitledge,
1
Se N = 1 e M = 1000000 i pode fazer melhor do que O (N + M), então outra solução é aplicar a busca binária em cada linha que traz O (N * log (M)) onde N <M no caso de isso resultar constante menor.
Luka Rahne,
1
Fiz alguns testes usando o seu método e o método de busca binária e postei os resultados AQUI . Parece que o método ziguezague é o melhor, a menos que eu não tenha conseguido gerar adequadamente as condições do pior caso para ambos os métodos.
The111 de
1
Bom uso de referências! No entanto, quando M==Nqueremos O(N)complexidade, não O(N*log(N/N))porque a última é zero. Um limite agudo "unificado" correto é O(N*(log(M/N)+1))quando N<=M.
hardmath de
35

Este problema leva Θ(b lg(t))tempo, onde b = min(w,h)e t=b/max(w,h). Discuto a solução nesta postagem do blog .

Limite inferior

Um adversário pode forçar um algoritmo a fazer Ω(b lg(t))consultas, restringindo-se à diagonal principal:

Adversário usando diagonal principal

Legenda: células brancas são itens menores, células cinzas são itens maiores, células amarelas são itens menores ou iguais e células laranja são itens maiores ou iguais. O adversário força a solução a ser a célula amarela ou laranja que o algoritmo consultar por último.

Observe que existem blistas classificadas independentes de tamanho t, exigindo Ω(b lg(t))consultas para eliminar completamente.

Algoritmo

  1. (Assuma sem perda de generalidade que w >= h )
  2. Compare o item de destino com a célula t à esquerda do canto superior direito da área válida
    • Se o item da célula corresponder, retorne a posição atual.
    • Se o item da célula for menor que o item de destino, elimine o restante t células na linha com uma pesquisa binária. Se um item correspondente for encontrado ao fazer isso, retorne com sua posição.
    • Caso contrário, o item da célula é mais do que o item de destino, eliminando tcolunas curtas.
  3. Se não houver nenhuma área válida restante, retornar falha
  4. Vá para a etapa 2

Encontrar um item:

Encontrar um item

Determinar que um item não existe:

Determinar que um item não existe

Legenda: células brancas são itens menores, células cinzas são itens maiores e a célula verde é um item igual.

Análise

Existem b*tcolunas curtas para eliminar. Existem blongas filas para eliminar. Eliminar uma longa fila custa O(lg(t))tempo. A eliminação de tcolunas curtas custa O(1)tempo.

No pior caso, teremos que eliminar cada coluna e cada linha, demorando O(lg(t)*b + b*t*1/t) = O(b lg(t)) .

Observe que estou assumindo lggrampos para um resultado acima de 1 (ou seja lg(x) = log_2(max(2,x))). É por isso que quando w=h, ou seja t=1, obtemos o limite esperado de O(b lg(1)) = O(b) = O(w+h).

Código

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
fonte
1
Interessante e possivelmente parcialmente fora da minha cabeça. Não estou familiarizado com esse estilo "adversário" de análise da complexidade. O adversário está realmente de alguma forma alterando dinamicamente o array enquanto você pesquisa, ou ele é apenas um nome dado à má sorte que você encontra na pesquisa do pior caso?
The111 de
2
@ The111 Má sorte é equivalente a alguém escolher um caminho ruim que não viola as coisas vistas até agora, então ambas as definições funcionam da mesma forma. Na verdade, estou tendo problemas para encontrar links que explicam a técnica especificamente com respeito à complexidade computacional ... Achei que essa era uma ideia muito mais conhecida.
Craig Gidney
Como log (1) = 0, a estimativa de complexidade deve ser dada como em O(b*(lg(t)+1))vez de O(b*lg(t)). Bom artigo, esp. por chamar a atenção para a "técnica do adversário" ao mostrar o limite do "pior caso".
hardmath de
@hardmath menciono isso na resposta. Eu esclareci um pouco.
Craig Gidney
17

Eu usaria a estratégia de dividir e conquistar para esse problema, semelhante ao que você sugeriu, mas os detalhes são um pouco diferentes.

Esta será uma pesquisa recursiva nos subintervalos da matriz.

Em cada etapa, escolha um elemento no meio do intervalo. Se o valor encontrado for o que você está procurando, está feito.

Caso contrário, se o valor encontrado for menor do que o valor que você está procurando, então você sabe que ele não está no quadrante acima e à esquerda de sua posição atual. Portanto, pesquise recursivamente os dois subintervalos: tudo (exclusivamente) abaixo da posição atual e tudo (exclusivamente) à direita que está na posição atual ou acima.

Caso contrário, (o valor encontrado é maior do que o valor que você está procurando) você sabe que não está no quadrante abaixo e à direita de sua posição atual. Portanto, pesquise recursivamente os dois subintervalos: tudo (exclusivamente) à esquerda da posição atual e tudo (exclusivamente) acima da posição atual que está na coluna atual ou em uma coluna à direita.

E ba-da-bing, você encontrou.

Observe que cada chamada recursiva lida apenas com o subintervalo atual, não (por exemplo) TODAS as linhas acima da posição atual. Apenas aqueles no subintervalo atual.

Aqui estão alguns pseudocódigos para você:

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
fonte
+1: Esta é uma estratégia O (log (N)) e, portanto, é a melhor ordem que se pode obter.
Rex Kerr
3
@Rex Kerr - Parece O (log (N)), já que isso é o que uma pesquisa binária normal é, no entanto, observe que existem potencialmente duas chamadas recursivas em cada nível. Isso significa que é muito pior do que o logarítmico simples. Não acredito que o pior caso seja melhor do que O (M + N), uma vez que, potencialmente, cada linha ou cada coluna deve ser pesquisada. Eu imagino que esse algoritmo poderia vencer o pior caso para muitos valores, no entanto. E a melhor parte é que ele é paralelizável, já que é para onde o hardware está indo ultimamente.
Jeffrey L Whitledge,
1
@JLW: É O (log (N)) - mas na verdade é O (log_ (4/3) (N ^ 2)) ou algo parecido. Veja a resposta de Svante abaixo. Na verdade, sua resposta é a mesma (se você quis dizer recursiva da maneira que acho).
Rex Kerr
1
@Svante - Os subarrays não se sobrepõem. Na primeira opção, eles não têm nenhum elemento y em comum. Na segunda opção, eles não têm nenhum elemento x em comum.
Jeffrey L Whitledge,
1
Não tenho certeza se isso é logarítmico. Calculei a complexidade usando a relação de recorrência aproximada T (0) = 1, T (A) = T (A / 2) + T (A / 4) + 1, onde A é a área de pesquisa, e terminei com T ( A) = O (Fib (lg (A))), que é aproximadamente O (A ^ 0,7) e pior do que O (n + m) que é O (A ^ 0,5). Talvez eu tenha cometido algum erro estúpido, mas parece que o algoritmo está perdendo muito tempo descendo ramos infrutíferos.
Craig Gidney
6

As duas respostas principais fornecidas até agora parecem ser o O(log N)método indiscutivelmente "ZigZag" e o O(N+M)método de pesquisa binária. Pensei em fazer alguns testes comparando os dois métodos com várias configurações. Aqui estão os detalhes:

A matriz é N x N quadrados em todos os testes, com N variando de 125 a 8000 (o maior que meu heap JVM poderia suportar). Para cada tamanho de array, escolhi um local aleatório no array para colocar um único 2. Em seguida, coloquei um em 3todos os lugares possíveis (à direita e abaixo do 2) e, em seguida, preenchi o resto da matriz com1. Alguns dos comentaristas anteriores pareciam pensar que esse tipo de configuração resultaria no pior caso de tempo de execução para ambos os algoritmos. Para cada tamanho de array, escolhi 100 locais aleatórios diferentes para os 2 (alvo de pesquisa) e executei o teste. Registrei o tempo médio de execução e o tempo de execução do pior caso para cada algoritmo. Como estava acontecendo muito rápido para obter boas leituras de ms em Java e porque não confio no nanoTime () do Java, repeti cada teste 1000 vezes apenas para adicionar um fator de polarização uniforme a todas as vezes. Aqui estão os resultados:

insira a descrição da imagem aqui

ZigZag beat binário em cada teste para tempos médios e de pior caso, no entanto, eles estão todos dentro de uma ordem de magnitude um do outro mais ou menos.

Aqui está o código 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");
    }
}
The111
fonte
1
+1 Oba, dados. :) Também pode ser interessante ver como essas duas abordagens funcionam em arrays NxM, uma vez que a busca binária parece que deve se tornar intuitivamente mais útil quanto mais abordamos um caso unidimensional.
Nate Kohl
5

Esta é uma pequena prova do limite inferior do problema.

Você não pode fazer isso melhor do que o tempo linear (em termos de dimensões da matriz, não o número de elementos). Na matriz abaixo, cada um dos elementos marcados como *pode ser 5 ou 6 (independentemente dos outros). Portanto, se seu valor-alvo for 6 (ou 5), o algoritmo precisa examinar todos eles.

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

É claro que isso se expande para arrays maiores também. Isso significa que essa resposta é ótima.

Atualização: Como apontado por Jeffrey L Whitledge, é apenas ideal como o limite inferior assintótico no tempo de execução vs tamanho dos dados de entrada (tratado como uma única variável). O tempo de execução tratado como função de duas variáveis ​​em ambas as dimensões da matriz pode ser melhorado.

Rafał Dowgird
fonte
Você não demonstrou que essa resposta é ótima. Considere, por exemplo, uma matriz com dez de largura e um milhão para baixo, na qual a quinta linha contém valores todos superiores ao valor de destino. Nesse caso, o algoritmo proposto fará uma pesquisa linear até 999.995 valores antes de chegar perto do alvo. Um algoritmo bifurcado como o meu pesquisará apenas 18 valores antes de se aproximar do alvo. E seu desempenho (assintoticamente) não é pior do que o algoritmo proposto em todos os outros casos.
Jeffrey L Whitledge,
@Jeffrey: É um limite inferior do problema para o caso pessimista. Você pode otimizar para boas entradas, mas existem entradas onde você não pode fazer melhor do que linear.
Rafał Dowgird
Sim, existem entradas onde você não pode fazer melhor do que linear. Nesse caso, meu algoritmo realiza essa pesquisa linear. Mas existem outras entradas em que você pode fazer muito melhor do que linear. Assim, a solução proposta não é ótima, pois sempre faz uma busca linear.
Jeffrey L Whitledge,
Isso mostra que o algoritmo deve levar tempo BigOmega (min (n, m)), não BigOmega (n + m). É por isso que você pode fazer muito melhor quando uma dimensão é significativamente menor. Por exemplo, se você sabe que haverá apenas 1 linha, poderá resolver o problema em tempo logarítmico. Acho que um algoritmo ideal levará tempo O (min (n + m, n lg m, m lg n)).
Craig Gidney
Atualizou a resposta de acordo.
Rafał Dowgird,
4

Acho que aqui está a resposta e funciona para qualquer tipo de matriz classificada

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
fonte
1

Pergunta interessante. Considere esta ideia - crie um limite onde todos os números sejam maiores que o seu alvo e outro onde todos os números sejam menores que o seu alvo. Se sobrar alguma coisa entre os dois, esse é o seu alvo.

Se estou procurando 3 em seu exemplo, leio na primeira linha até chegar a 4 e, a seguir, procuro o menor número adjacente (incluindo diagonais) maior que 3:

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

Agora faço o mesmo para os números menores que 3:

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

Agora eu pergunto, há alguma coisa dentro dos dois limites? Se sim, deve ser 3. Se não, então não há 3. Meio indireto, já que não encontro o número, apenas deduzo que deve estar lá. Isso tem o bônus adicional de contar TODOS os 3.

Eu tentei isso em alguns exemplos e parece funcionar bem.

Grembo
fonte
Um voto negativo sem comentários? Acho que é O (N ^ 1/2), pois o desempenho do pior caso requer uma verificação da diagonal. Pelo menos me mostre um contra-exemplo em que esse método não funcione!
Grembo
+1: boa solução ... criativa, e boa que encontra todas as soluções.
Tony Delroy,
1

A pesquisa binária na diagonal do array é a melhor opção. Podemos descobrir se o elemento é menor ou igual aos elementos da diagonal.

Nikhil KR
fonte
0

A. Faça uma pesquisa binária nas linhas onde o número de destino pode estar.

B. Faça um gráfico: procure o número pegando sempre o menor nó vizinho não visitado e retrocedendo quando um número muito grande for encontrado

Tuomas Pelkonen
fonte
0

A pesquisa binária seria a melhor abordagem, imo. Começando em 1/2 x, 1/2 y irá cortá-lo pela metade. Ou seja, um quadrado 5x5 seria algo como x == 2 / y == 3. Arredondei um valor para baixo e um valor para cima para uma zona melhor na direção do valor desejado.

Para maior clareza, a próxima iteração forneceria algo como x == 1 / y == 2 OU x == 3 / y == 5

Woot4Moo
fonte
0

Bem, para começar, vamos supor que estamos usando um quadrado.

1 2 3
2 3 4
3 4 5

1. Pesquisando um quadrado

Eu usaria uma pesquisa binária na diagonal. O objetivo é localizar o número menor que não seja estritamente inferior ao número de destino.

Digamos que estou procurando por 4exemplo, então acabaria localizando 5em(2,2) .

Então, tenho certeza de que se 4estiver na mesa, está em uma posição (x,2)ou (2,x)com xdentro [0,2]. Bem, são apenas 2 buscas binárias.

A complexidade não é assustadora: O(log(N))(3 pesquisas binárias em intervalos de comprimento N)

2. Procurando um retângulo, abordagem ingênua

Claro, fica um pouco mais complicado quando Ne Mdifere (com um retângulo), considere este caso degenerado:

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 digamos que estou procurando 9... A abordagem diagonal ainda é boa, mas a definição de diagonais muda. Aqui está minha diagonal [1, (5 or 6), 17]. Digamos que eu peguei [1,5,17], então sei que se 9está na tabela, também está na subparte:

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

Isso nos dá 2 retângulos:

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

Então, podemos recurse! provavelmente começando por aquele com menos elementos (embora neste caso nos mate).

Devo apontar que, se uma das dimensões for menor que 3, não podemos aplicar os métodos diagonais e devemos usar uma busca binária. Aqui, isso significaria:

  • Aplicar pesquisa binária em 10 11 12 13 14 15 16, não encontrado
  • Aplicar pesquisa binária em 5 6 7 8, não encontrado
  • Aplicar pesquisa binária em 6 7 8 9, não encontrado

É complicado porque para obter um bom desempenho, você pode querer diferenciar entre vários casos, dependendo da forma geral ....

3. Procurando um retângulo, abordagem brutal

Seria muito mais fácil se lidássemos com um quadrado ... então, vamos resolver as coisas.

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

Agora temos um quadrado.

Claro, provavelmente NÃO criaremos essas linhas, poderíamos simplesmente emulá-las.

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

então ele se comporta como um quadrado sem ocupar mais memória (ao custo da velocidade, provavelmente, dependendo do cache ... bem: p)

Matthieu M.
fonte
0

EDITAR:

Eu entendi mal a pergunta. Como os comentários apontam, isso só funciona no caso mais restrito.

Em uma linguagem como C, que armazena dados em ordem de linha maior, simplesmente trate-os como um array 1D de tamanho n * me use uma pesquisa binária.

Hugh Brackett
fonte
Sim, por que torná-lo mais complexo do que precisa ser.
erikkallen,
A matriz não está classificada, portanto, nenhuma pesquisa de bin pode ser aplicada a ela
Miollnyr
1
Isso só funcionará se o último elemento de cada linha for maior que o primeiro elemento da próxima linha, o que é um requisito muito mais restritivo do que o proposto pelo problema.
Jeffrey L Whitledge,
Obrigado, editei minha resposta. Não li com atenção suficiente, particularmente a matriz de exemplo.
Hugh Brackett
0

Eu tenho uma solução recursiva Divide & Conquer. A ideia básica para uma etapa é: sabemos que o Left-Upper (LU) é o menor e o right-bottom (RB) é o maior número, então o No (N) dado deve: N> = LU e N <= RB

IF N == LU e N == RB :::: Elemento encontrado e abortado retornando a posição / Índice Se N> = LU e N <= RB = FALSE, Não não existe e aborta. Se N> = LU e N <= RB = TRUE, divida a matriz 2D em 4 partes iguais da matriz 2D, cada uma de maneira lógica .. E, em seguida, aplique a mesma etapa de algo a todas as quatro submatrizes.

Meu Algo está correto Eu implementei no PC do meu amigo. Complexidade: cada 4 comparações podem ser usadas para deduzir o número total de elementos a um quarto em seu pior caso. Então, minha complexidade chega a ser 1 + 4 x lg (n) + 4 Mas realmente esperava que funcionasse em O (n)

Acho que algo está errado em algum lugar no meu cálculo de Complexidade, corrija se sim ..

Pervez Alam
fonte
0

A solução ideal é começar no canto superior esquerdo, que tem valor mínimo. Mova diagonalmente para baixo para a direita até atingir um elemento cujo valor> = valor do elemento fornecido. Se o valor do elemento for igual ao do elemento fornecido, retorna encontrado como verdadeiro.

Caso contrário, a partir daqui podemos proceder de duas maneiras.

Estratégia 1:

  1. Mova para cima na coluna e procure o elemento dado até chegarmos ao fim. Se encontrado, retorno encontrado como verdadeiro
  2. Mova para a esquerda na linha e procure o elemento fornecido até chegarmos ao fim. Se encontrado, retorno encontrado como verdadeiro
  3. retorno encontrado como falso

Estratégia 2: Deixe i denotar o índice da linha ej denotar o índice da coluna do elemento diagonal em que paramos. (Aqui, temos i = j, BTW). Seja k = 1.

  • Repita as etapas abaixo até ik> = 0
    1. Pesquise se a [ik] [j] é igual ao elemento fornecido. se sim, retorno encontrado como verdadeiro.
    2. Pesquise se a [i] [jk] é igual ao elemento fornecido. se sim, retorno encontrado como verdadeiro.
    3. Incremento k

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

Murali Mohan
fonte
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
fonte
0

Eu sugiro, armazene todos os personagens em a 2D list. em seguida, localize o índice do elemento necessário se ele existir na lista.

Se não estiver presente, imprima a mensagem apropriada, caso contrário, imprima a linha e a coluna como:

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

Isso incorrerá apenas no tempo de pesquisa binária em uma lista.

Por favor, sugira quaisquer correções. :)

Abhi31jeet
fonte
0

Se a solução O (M log (N)) estiver ok para uma matriz 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;
}

Demonstração de trabalho em C ++.

Por favor, deixe-me saber se isso não funcionaria ou se há um bug.

kaushal
fonte
0

Venho fazendo essa pergunta em entrevistas há quase uma década e acho que só uma pessoa foi capaz de criar um algoritmo ideal.

Minha solução sempre foi:

  1. Pesquisa binária na diagonal do meio, que é a diagonal descendo e à direita, contendo o item em (rows.count/2, columns.count/2).

  2. Se o número de destino for encontrado, retorna verdadeiro.

  3. Caso contrário, dois números ( ue v) terão sido encontrados de forma que useja menor do que o destino, vmaior do que o destino e vum à direita e outro abaixo u.

  4. Pesquise recursivamente a submatriz à direita ue no topo ve aquela na parte inferior ue à esquerda de v.

Eu acredito que esta é uma melhoria estrita em relação ao algoritmo fornecido por Nate aqui , uma vez que pesquisar na diagonal geralmente permite uma redução de mais da metade do espaço de pesquisa (se a matriz estiver próxima do quadrado), enquanto pesquisar uma linha ou coluna sempre resulta em uma eliminação exatamente da metade.

Aqui está o código em (provavelmente não terrivelmente 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)
        }
    }
}
digbybare
fonte
-1

Dada uma matriz quadrada da seguinte forma:

[abc]
[def]
[ijk]

Sabemos que a <c, d <f, i <k. O que não sabemos é se d <c ou d> c, etc. Temos garantias apenas em 1 dimensão.

Olhando para os elementos finais (c, f, k), podemos fazer uma espécie de filtro: é N <c? search (): next (). Assim, temos n iterações ao longo das linhas, com cada linha tomando O (log (n)) para pesquisa binária ou O (1) se filtrada.

Deixe-me dar um EXEMPLO onde N = j,

1) Verifique a linha 1. j <c? (não, vá em seguida)

2) Verifique a linha 2. j <f? (sim, a pesquisa bin não leva a nada)

3) Verifique a linha 3. j <k? (sim, o bin search o encontra)

Tente novamente com N = q,

1) Verifique a linha 1. q <c? (não, vá em seguida)

2) Verifique a linha 2. q <f? (não, vá em seguida)

3) Verifique a linha 3. q <k? (não, vá em seguida)

Provavelmente existe uma solução melhor, mas isso é fácil de explicar .. :)

apandit
fonte