Modo efficiente per cercare un elemento


88

Recentemente ho avuto un colloquio, in cui mi hanno posto una domanda "di ricerca ".
La domanda era:

Supponiamo che ci sia un array di numeri interi (positivi), di cui ogni elemento è +1o è -1confrontato con i suoi elementi adiacenti.

Esempio:

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

Ora cerca 7e restituisci la sua posizione.

Ho dato questa risposta:

Memorizzare i valori in una matrice temporanea, ordinarli e quindi applicare la ricerca binaria.

Se l'elemento viene trovato, restituisce la sua posizione nella matrice temporanea.
(Se il numero si verifica due volte, restituisci la sua prima occorrenza)

Ma non sembravano essere soddisfatti di questa risposta.

Qual è la risposta giusta?


4
Per quanto ne so, una ricerca lineare è un buon modo per individuare l'indice di un elemento nell'array. Non sono ancora sicuro di un altro algoritmo di ricerca che sia efficiente nell'individuare l'indice di un elemento.
Sean Francis N. Ballais

4
Se 7 è garantito che appaia solo una volta o se non importa quale 7 viene restituito, puoi migliorare ancora un po 'l'algoritmo lineare della risposta di coleman.
user1942027

52
Se la tua soluzione originale richiede l'ordinamento, è peggio della ricerca lineare ingenua. Sembra che tu non ne sia consapevole.
cubuspl42

5
L'ordinamento richiede O (nlogn) e una ricerca binaria è O (logn). Se è necessario cercare molti molti valori dalla matrice ampia, la risposta potrebbe essere migliore, ma se si cerca solo una volta, gli algoritmi O (n) potrebbero essere migliori.
jingyu9575

23
Non so perché nessun altro l'abbia menzionato: il tuo metodo non solo era inefficiente, ma non corretto , e questo è molto peggio della semplice inefficienza. Il requisito è per la posizione di un dato numero nella matrice originale . Il metodo restituisce la posizione del numero in un array ordinato . Ora, si potrebbe recuperare la posizione originale, convertendo la matrice semplice per una serie di tuple (numero, orig_pos) prima della cernita. Ma non l'hai detto, quindi immagino che tu non l'abbia menzionato nemmeno nell'intervista.
Tom Zych

Risposte:


126

È possibile eseguire una ricerca lineare con passaggi che sono spesso maggiori di 1. L'osservazione cruciale è che se ad es. array[i] == 4E 7 non è ancora apparso, il candidato successivo per 7 è all'indice i+3. Usa un ciclo while che va ripetutamente direttamente al prossimo candidato valido.

Ecco un'implementazione, leggermente generalizzata. Trova la prima occorrenza di knell'array (soggetta alla restrizione + = 1) o -1se non si verifica:

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

produzione:

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

8
Proprio quello che stavo pensando. Questo è O(N), ma non credo che ci sia un modo più veloce per farlo.
shapiro yaacov

2
Potresti farlo in media un po 'più velocemente con più candidati (ad esempio il primo e l'ultimo), e poi andare con quello più vicino all'obiettivo, cioè se hai solo bisogno di trovare una singola occorrenza, non la prima.
mkadunc

2
@mkadunc Questa è una buona idea. Un'altra osservazione è che se il primo e l'ultimo elemento si trovano a cavallo del 7, in quel caso speciale puoi usare una ricerca binaria (se non ti interessa quale 7 trovi)
John Coleman

1
Nel caso in cui sia necessario trovare 7 (non necessariamente il primo), propongo il seguente miglioramento (pratico). Crea un elenco di sezioni (due numeri interi, "inizio" e "fine") e invece di iniziare dall'inizio della matrice, inizia nel mezzo. In base al valore nella cella, ignora l'intervallo pertinente e aggiungi le due sezioni rimaste al tuo elenco di sezioni. Ora ripeti per l'elemento successivo nell'elenco. Questo è ancora "O (n)", ma ignori il doppio dell'intervallo ogni volta che controlli una cella.
shapiro yaacov

3
@ShapiroYaacov: combinato con il controllo se l'intervallo dal più basso al più alto dei valori su entrambi i lati di una sezione include k (7), questo merita una risposta a sé stante.
barba grigia

35

Il tuo approccio è troppo complicato. Non è necessario esaminare ogni elemento dell'array. Il primo valore è 4, quindi 7è almeno 7-4 gli elementi di distanza, e si può saltare quelli.

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

Uscita del programma:

Steps 4, index 11

Modifica: migliorata dopo i commenti di @Raphael Miedl e @Martin Zabel.


2
Un pelo nell'uovo, if ((skip = 7 - array[i]) < 1) skip = 1;sembra complicarlo e pessimizzarlo a mio parere. Se array[i] == 200ottieni -193e salti di 1 ogni volta anche se potresti saltare tutti i 193. Perché non solo i += abs(7 - array[i])?
user1942027

1
È necessario impostare skipla differenza assoluta tra 7 e array[i].
Martin Zabel

@Raphael Miedl no, un elemento non sarà 200, saresti passato 7.
Banderuola

3
@WeatherVane non abbiamo questa garanzia, solo che i valori adiacenti sono +1/ l' -1uno dall'altro. Quindi potrebbe essere solo array[0] == 200e gli altri sono per lo più -1.
user1942027

1
@WeatherVane presuppone che l'elemento si trovi sempre nell'array, il che potrebbe non essere il caso. -1 è un ritorno valido in quel caso; che cambia un po 'il codice che hai
Eugene

20

Una variazione della ricerca lineare convenzionale potrebbe essere un buon modo per procedere. Scegliamo un elemento diciamo array[i] = 2. Ora, array[i + 1]sarà 1 o 3 (dispari), array[i + 2]sarà (solo numeri interi positivi) 2 o 4 (numero pari).

Continuando in questo modo, è array[i + 2*n]possibile osservare un modello: conterrà numeri pari e quindi tutti questi indici possono essere ignorati.

Inoltre, possiamo vederlo

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

quindi, index i + 5dovrebbe essere controllato successivamente e un ciclo while può essere usato per determinare l'indice successivo da controllare, a seconda del valore trovato in index i + 5.

Mentre questo ha complessità O(n)(tempo lineare in termini di complessità asintotica), è meglio di una normale ricerca lineare in termini pratici poiché tutti gli indici non sono visitati.

Ovviamente, tutto questo sarà invertito se array[i](il nostro punto di partenza) fosse dispari.


8

L'approccio presentato da John Coleman è ciò che l'intervistatore sperava, con ogni probabilità.
Se sei disposto ad andare un po 'più complicato, puoi aumentare la lunghezza di salto prevista:
Chiama il valore target k . Inizia con il valore v del primo elemento nella posizione pe chiama la differenza kv dv con valore assoluto av . Per velocizzare le ricerche negative, dai un'occhiata all'ultimo elemento come l'altro valore u nella posizione o: se dv × du è negativo, k è presente (se qualsiasi occorrenza di k è accettabile, puoi restringere l'intervallo dell'indice qui come fa la ricerca binaria). Se av + au è maggiore della lunghezza dell'array, k è assente. (Se dv × du è zero, v oppure u uguale k.)
Tralasciando indice di validità: Sonda il ( "next"), posizione in cui la sequenza potrebbe tornare a v con k nel mezzo: o = p + 2*av.
Se dv × du è negativo, trova k (ricorsivamente?) Da p + av a o-au;
se è zero, u è uguale a k in o.
Se du è uguale a dv e il valore nel mezzo non è k, o au supera av,
o non riesci a trovare k da p + av a o-au,
lascia p=o; dv=du; av=au;e continua a sondare.
(Per un flash-back completo dei testi degli anni '60, guarda con Courier. Il mio "1 ° 2 ° pensiero" era usareo = p + 2*av - 1, che preclude du uguale a dv .)


3

PASSO 1

Inizia con il primo elemento e controlla se è 7. Diciamo che cè l'indice della posizione corrente. Così, inizialmente, c = 0.

PASSO 2

Se è 7, hai trovato l'indice. È c. Se hai raggiunto la fine della matrice, scoppia.

FASE 3

In caso contrario, 7 devono trovarsi ad almeno |array[c]-7|posizioni di distanza perché puoi aggiungere solo un'unità per indice. Pertanto, aggiungi |array[c]-7|al tuo indice corrente, c, e vai di nuovo al PASSAGGIO 2 per controllare.

Nel peggiore dei casi, quando ci sono 1 e -1 alternati, la complessità temporale può raggiungere O (n), ma i casi medi sarebbero consegnati rapidamente.


In che modo è diverso dalla risposta di John Coleman? (Oltre a suggerire |c-7|dove |array[c]-7|sembra necessario.)
Barba grigia

Ho appena visto la sua risposta. Ammetto che l'idea centrale è la stessa.
Akeshwar Jha

La domanda originale non prevede che l'array inizi con un numero inferiore a 7. Quindi array[c]-7può essere positivo o negativo. È necessario applicare abs()ad esso prima del salto in avanti.
arielf il

Sì hai ragione. È per questo che sto usando array[c] - 7con l'operatore modulo, |array[c] - 7|.
Akeshwar Jha

3

Qui sto dando l'implementazione in 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
Codice non documentato in una lingua con una convenzione almeno semi-ufficiale . In che modo questo è diverso dalle risposte di John Coleman e Akeshwar, oltre a interpretare liberamente il tag "c"?
barba grigia

3

Ecco una soluzione in stile divide et impera. A scapito di (molto) più contabilità, possiamo saltare più elementi; invece di scansionare da sinistra a destra, prova al centro e salta in entrambe le direzioni.

#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 la tua risposta non restituirà la prima occorrenza ma qualsiasi occorrenza casuale dell'elemento di ricerca poiché inizi dal centro e salti da entrambi i lati.
Ram Patra

Cambiare l'ordine di ricorsione è lasciato come esercizio al lettore.
Neal Fultz

1
neal-fultz quindi modifica il messaggio nella chiamata al metodo 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);

Voleva includere una soluzione ricorsiva al problema. Godere

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.