Maneira eficiente de pesquisar um elemento


88

Recentemente, tive uma entrevista, onde me fizeram uma pergunta " investigativa ".
A questão era:

Assuma que existe uma matriz de números inteiros (positivos), dos quais cada elemento é ou +1ou -1em relação aos seus elementos adjacentes.

Exemplo:

array = [4,5,6,5,4,3,2,3,4,5,6,7,8];

Agora procure 7e retorne sua posição.

Eu dei esta resposta:

Armazene os valores em uma matriz temporária, classifique-os e aplique a pesquisa binária.

Se o elemento for encontrado, retorne sua posição na matriz temporária.
(Se o número estiver ocorrendo duas vezes, retorne a primeira ocorrência)

Mas eles não pareciam satisfeitos com esta resposta.

Qual é a resposta certa?


4
Pelo que eu sei, uma pesquisa linear é uma boa maneira de localizar o índice de um elemento na matriz. Não tenho certeza ainda de outro algoritmo de busca que seja eficiente para localizar o índice de um elemento.
Sean Francis N. Ballais

4
Se 7 é garantido que apareça apenas uma vez ou se não importa qual 7 é retornado, você pode melhorar um pouco mais o algoritmo linear da resposta de Coleman.
user1942027

52
Se a sua solução original requer classificação, é pior do que a busca linear ingênua. Você parece não estar ciente disso.
cubuspl42

5
A classificação requer O (nlogn), e uma pesquisa binária é O (logn). Se você precisar pesquisar muitos e muitos valores no grande array, sua resposta pode ser melhor, mas se você pesquisar apenas uma vez, algoritmos O (n) podem ser melhores.
jingyu9575

23
Não sei por que ninguém mais mencionou isso: seu método não era apenas ineficiente, era incorreto e isso é muito pior do que a mera ineficiência. O requisito é para a posição de um determinado número na matriz original . Seu método retorna a posição do número em uma matriz classificada . Agora, você pode recuperar a posição original, convertendo o array simples em um array de tuplas (número, orig_pos) antes de classificar. Mas você não mencionou isso, então suponho que também não mencionou na entrevista.
Tom Zych

Respostas:


126

Você pode fazer uma pesquisa linear com etapas que geralmente são maiores do que 1. A observação crucial é que, se eg array[i] == 4e 7 ainda não apareceram, o próximo candidato para 7 está no índice i+3. Use um loop while que repetidamente vai diretamente para o próximo candidato viável.

Aqui está uma implementação, ligeiramente generalizada. Encontra a primeira ocorrência de kna matriz (sujeito à restrição + = 1) ou -1se não ocorrer:

#include <stdio.h>
#include <stdlib.h>

int first_occurence(int k, int array[], int n);

int main(void){
    int a[] = {4,3,2,3,2,3,4,5,4,5,6,7,8,7,8};
    printf("7 first occurs at index %d\n",first_occurence(7,a,15));
    printf("but 9 first \"occurs\" at index %d\n",first_occurence(9,a,15));
    return 0;
}

int first_occurence(int k, int array[], int n){
    int i = 0;
    while(i < n){
        if(array[i] == k) return i;
        i += abs(k-array[i]);
    }
    return -1;
}

resultado:

7 first occurs at index 11
but 9 first "occurs" at index -1

8
Precisamente o que eu estava pensando. Isso é O(N), mas não acho que haja uma maneira mais rápida de fazer isso.
shapiro yaacov

2
Você poderia fazer isso um pouco mais rápido, em média, com mais candidatos (por exemplo, primeiro e último) e, em seguida, escolher aquele que estiver mais próximo do alvo - ou seja, se você só precisar encontrar uma única ocorrência, não a primeira.
mkadunc

2
@mkadunc Essa é uma boa ideia. Outra observação é que se o primeiro e o último elementos abrangem 7, então, nesse caso especial, você pode usar uma pesquisa binária (se não se importar com qual 7 encontrar)
John Coleman

1
No caso em que você precisa encontrar qualquer 7 (não necessariamente o primeiro), proponho a seguinte melhoria (prática). Faça uma lista de seções (dois inteiros, 'início' e 'fim') e em vez de começar no início da matriz, comece no meio. De acordo com o valor na célula, ignore o intervalo relevante e adicione as duas seções restantes à sua lista de seções. Agora repita para o próximo item da lista. Ainda é 'O (n)', mas você ignora o dobro do intervalo toda vez que verifica uma célula.
shapiro yaacov

3
@ShapiroYaacov: Combinado com a verificação de se o intervalo do menor ao maior dos valores para ambos os lados de uma seção inclui k (7), isso merece uma resposta própria.
Barba Cinzenta

35

Sua abordagem é muito complicada. Você não precisa examinar todos os elementos da matriz. O primeiro valor é 4, portanto 7está, pelo menos, 7-4 elementos ausentes, e você pode pular esses.

#include <stdio.h>
#include <stdlib.h>

int main (void)
{
    int array[] = {4,5,6,5,4,3,2,3,4,5,6,7,8};
    int len = sizeof array / sizeof array[0];
    int i = 0;
    int steps = 0;
    while (i < len && array[i] != 7) {
        i += abs(7 - array[i]);
        steps++;
    }

    printf("Steps %d, index %d\n", steps, i);
    return 0;
}

Resultado do programa:

Steps 4, index 11

Editar: melhorado após comentários de @Raphael Miedl e @Martin Zabel.


2
Um detalhismo if ((skip = 7 - array[i]) < 1) skip = 1;parece complicar demais e pessimá-lo na minha opinião. Se array[i] == 200você obtiver -193e apenas pular 1 a cada vez, embora possa pular todos os 193. Por que não apenas i += abs(7 - array[i])?
user1942027

1
Você deve definir skipa diferença absoluta entre 7 e array[i].
Martin Zabel

@Raphael Miedl não, um elemento não será 200, você teria passado 7.
Weather Vane

3
@WeatherVane não temos essa garantia, apenas que os valores adjacentes são +1/ -1uns dos outros. Então pode ser apenas array[0] == 200e os outros são em sua maioria -1.
user1942027

1
@WeatherVane pressupõe que o elemento sempre é encontrado na matriz, o que pode não ser o caso. -1 é um retorno válido nesse caso; que muda bastante o código que você tem
Eugene

20

Uma variação da pesquisa linear convencional pode ser um bom caminho a percorrer. Vamos escolher um elemento, digamos array[i] = 2. Agora, array[i + 1]será 1 ou 3 (ímpar), array[i + 2]será (somente números inteiros positivos) 2 ou 4 (número par).

Ao continuar assim, um padrão é observável - array[i + 2*n]manterá números pares e, portanto, todos esses índices podem ser ignorados.

Além disso, podemos ver que

array[i + 3] = 1 or 3 or 5
array[i + 5] = 1 or 3 or 5 or 7

portanto, o índice i + 5deve ser verificado em seguida e um loop while pode ser usado para determinar o próximo índice a ser verificado, dependendo do valor encontrado no índice i + 5.

Embora isso tenha complexidade O(n)(tempo linear em termos de complexidade assintótica), é melhor do que uma pesquisa linear normal em termos práticos, pois todos os índices não são visitados.

Obviamente, tudo isso será revertido se array[i](nosso ponto de partida) for estranho.


8

A abordagem apresentada por John Coleman é o que o entrevistador esperava, com toda a probabilidade.
Se você estiver disposto a ir um pouco mais complicado, pode aumentar a duração esperada do salto:
Chame o valor alvo k . Comece com o valor do primeiro elemento v na posição p e chame a diferença kv dv com o valor absoluto av . Para acelerar pesquisas negativas, dê uma olhada no último elemento como o outro valor u na posição o: se dv × du for negativo, k está presente (se qualquer ocorrência de k for aceitável, você pode restringir o intervalo do índice aqui da forma como a pesquisa binária faz). Se av + au for maior que o comprimento da matriz, k estará ausente. (Se dv × du é zero, V ou L é igual a k.)
Omitindo validade índice: Sonda o ( "Avançar") posição onde a sequência pode voltar a v com k no meio: o = p + 2*av.
Se dv × du for negativo, encontre k (recursivamente?) De p + av para o-au;
se for zero, u é igual a k em o.
Se du é igual a dv e o valor no meio não é k, ou au excede av,
ou você não consegue encontrar k de p + av para o-au,
deixe p=o; dv=du; av=au;e continue sondando.
(Para um flashback completo dos textos dos anos 60, veja com o Courier. Meu "primeiro segundo pensamento" foi usaro = p + 2*av - 1, o que exclui du equals dv .)


3

PASSO 1

Comece com o primeiro elemento e verifique se é 7. Digamos que cseja o índice da posição atual. Portanto, inicialmente c = 0,.

PASSO 2

Se for 7, você encontrou o índice. É c. Se você alcançou o final do array, saia.

ETAPA 3

Se não for, então 7 devem estar pelo menos |array[c]-7|posições de distância, porque você só pode adicionar uma unidade por índice. Portanto, adicione |array[c]-7|ao seu índice atual, c, e vá para o PASSO 2 novamente para verificar.

No pior caso, quando há alternativos 1 e -1s, a complexidade do tempo pode chegar a O (n), mas os casos médios seriam entregues rapidamente.


Como isso difere da resposta de John Coleman? (Além de sugerir |c-7|onde |array[c]-7|parece necessário.)
Barba Cinzenta

Acabei de ver sua resposta. Admito que a ideia central é a mesma.
Akeshwar Jha

A pergunta original não estipula que o array comece com um número menor que 7. Portanto, array[c]-7pode ser positivo ou negativo. Você precisa se inscrever abs()antes de avançar.
arielf

Sim, você está certo. É por isso que estou usando array[c] - 7com o operador de módulo |array[c] - 7|,.
Akeshwar Jha

3

Aqui estou dando a implementação em java ...

public static void main(String[] args) 
{       
    int arr[]={4,5,6,5,4,3,2,3,4,5,6,7,8};
    int pos=searchArray(arr,7);

    if(pos==-1)
        System.out.println("not found");
    else
        System.out.println("position="+pos);            
}

public static int searchArray(int[] array,int value)
{
    int i=0;
    int strtValue=0;
    int pos=-1;

    while(i<array.length)
    {
        strtValue=array[i];

        if(strtValue<value)
        {
            i+=value-strtValue;
        }
        else if (strtValue==value)
        {
            pos=i;
            break;
        }
        else
        {
            i=i+(strtValue-value);
        }       
    }

    return pos;
}

2
Código não documentado em um idioma com uma convenção pelo menos semi-oficial . Como isso é diferente das respostas de John Coleman e Akeshwar, além de interpretar a tag "c" liberalmente?
Barba Cinzenta

3

Aqui está uma solução de estilo dividir e conquistar. À custa de (muito) mais contabilidade, podemos pular mais elementos; em vez de digitalizar da esquerda para a direita, teste no meio e pule nas duas direções.

#include <stdio.h>                                                               
#include <math.h>                                                                

int could_contain(int k, int left, int right, int width);                        
int find(int k, int array[], int lower, int upper);   

int main(void){                                                                  
    int a[] = {4,3,2,3,2,3,4,5,4,5,6,7,8,7,8};                                   
    printf("7 first occurs at index %d\n",find(7,a,0,14));                       
    printf("but 9 first \"occurs\" at index %d\n",find(9,a,0,14));               
    return 0;                                                                    
}                                                                                

int could_contain(int k, int left, int right, int width){                        
  return (width >= 0) &&                                                         
         (left <= k && k <= right) ||                                            
         (right <= k && k <= left) ||                                            
         (abs(k - left) + abs(k - right) < width);                               
}                                                                                

int find(int k, int array[], int lower, int upper){                              
  //printf("%d\t%d\n", lower, upper);                                            

  if( !could_contain(k, array[lower], array[upper], upper - lower )) return -1;  

  int mid = (upper + lower) / 2;                                                 

  if(array[mid] == k) return mid;                                                

  lower = find(k, array, lower + abs(k - array[lower]), mid - abs(k - array[mid]));
  if(lower >= 0 ) return lower;                                                    

  upper = find(k, array, mid + abs(k - array[mid]), upper - abs(k - array[upper]));
  if(upper >= 0 ) return upper;                                                  

  return -1;                                                                     

}

neal-fultz sua resposta não retornará a primeira ocorrência, mas qualquer ocorrência aleatória do elemento de pesquisa, pois você está começando do meio e pulando de qualquer lado.
Ram Patra

A mudança da ordem de recursão é deixada como um exercício para o leitor.
Neal Fultz

1
neal-fultz então edite a mensagem em sua chamada de método printf ().
Ram Patra

1

const findMeAnElementsFunkyArray = (arr, ele, i) => {
  const elementAtCurrentIndex = arr[i];

  const differenceBetweenEleAndEleAtIndex = Math.abs(
    ele - elementAtCurrentIndex
  );

  const hop = i + differenceBetweenEleAndEleAtIndex;

  if (i >= arr.length) {
    return;
  }
  if (arr[i] === ele) {
    return i;
  }

  const result = findMeAnElementsFunkyArray(arr, ele, hop);

  return result;
};

const array = [4,5,6,5,4,3,2,3,4,5,6,7,8];

const answer = findMeAnElementsFunkyArray(array, 7, 0);

console.log(answer);

Queria incluir uma solução recursiva para o problema. Aproveitar

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.