Esiste un algoritmo efficiente diverso dalla ricerca della forza bruta per trovare i tre numeri interi?
Sì; possiamo risolverlo in O (n 2 ) tempo! Innanzitutto, considera che il tuo problema P
può essere espresso in modo equivalente in un modo leggermente diverso che elimina la necessità di un "valore target":
problema originale P
: dati un array A
di n
numeri interi e un valore target S
, esiste una 3 tupla da A
quella somma a S
?
problema modificato P'
: data una matrice A
di n
numeri interi, esiste una 3 tupla da A
quella somma a zero?
Si noti che si può andare da questa versione del problema P'
dal P
sottraendo la vostra S / 3 da ogni elemento A
, ma ora non è necessario più il valore di riferimento.
Chiaramente, se semplicemente testassimo tutte le possibili 3 tuple, risolveremmo il problema in O (n 3 ) - questa è la linea di base della forza bruta. È possibile fare di meglio? E se prendessimo le tuple in un modo un po 'più intelligente?
Innanzitutto, investiamo del tempo per ordinare l'array, il che ci costa una penalità iniziale di O (n log n). Ora eseguiamo questo algoritmo:
for (i in 1..n-2) {
j = i+1 // Start right after i.
k = n // Start at the end of the array.
while (k >= j) {
// We got a match! All done.
if (A[i] + A[j] + A[k] == 0) return (A[i], A[j], A[k])
// We didn't match. Let's try to get a little closer:
// If the sum was too big, decrement k.
// If the sum was too small, increment j.
(A[i] + A[j] + A[k] > 0) ? k-- : j++
}
// When the while-loop finishes, j and k have passed each other and there's
// no more useful combinations that we can try with this i.
}
Questo algoritmo funziona inserendo tre puntatori, i
, j
, e k
in vari punti della matrice. i
inizia all'inizio e lentamente si fa strada fino alla fine. k
indica l'ultimo elemento. j
indica dove i
è iniziato. Proviamo iterativamente a sommare gli elementi nei rispettivi indici e ogni volta si verifica una delle seguenti condizioni:
- La somma è esattamente giusta! Abbiamo trovato la risposta.
- La somma era troppo piccola. Spostare
j
più vicino alla fine per selezionare il più grande numero successivo.
- La somma era troppo grande. Avvicinati
k
all'inizio per selezionare il numero più piccolo successivo.
Per ciascuno i
, i puntatori j
e k
si avvicineranno gradualmente l'uno all'altro. Alla fine si passeranno l'un l'altro, e a quel punto non avremo bisogno di provare nient'altro per questo i
, dato che sommeremmo gli stessi elementi, solo in un ordine diverso. Dopo quel punto, proviamo il prossimo i
e ripetiamo.
Alla fine esauriremo le possibilità utili o troveremo la soluzione. Potete vedere che questo è O (n 2 ) poiché eseguiamo il ciclo esterno O (n) volte e eseguiamo il ciclo interno O (n) volte. È possibile farlo in modo sub-quadratico se si diventa davvero fantasiosi, rappresentando ogni numero intero come un vettore bit ed eseguendo una trasformata di Fourier veloce, ma questo va oltre lo scopo di questa risposta.
Nota: poiché questa è una domanda di intervista, ho tradito un po 'qui: questo algoritmo consente la selezione dello stesso elemento più volte. Cioè, (-1, -1, 2) sarebbe una soluzione valida, come lo sarebbe (0, 0, 0). Trova anche solo le risposte esatte , non la risposta più vicina, come menziona il titolo. Come esercizio per il lettore, ti farò capire come farlo funzionare solo con elementi distinti (ma è un cambiamento molto semplice) e risposte esatte (che è anche un semplice cambiamento).