Czy istnieje inny skuteczny algorytm niż wyszukiwanie siłowe do znalezienia trzech liczb całkowitych?
Tak; można rozwiązać w O (n = 2 ) Czas! Po pierwsze, zastanów się, że problem P
można sformułować równoważnie w nieco inny sposób, co eliminuje potrzebę określenia „wartości docelowej”:
Oryginalny problemu P
: Biorąc pod tablicę A
z n
liczb i wartości docelowej S
, czy istnieje taka 3-krotki z A
tym, że sum S
?
Problem modyfikowany P'
: Biorąc pod tablicę A
z n
liczb całkowitych, czy istnieje taka 3-krotki z A
tym, że kwoty do zera?
Zauważ że można przejść z tą wersją problemu P'
z P
odejmując swój S / 3 z każdego elementu A
, ale teraz nie trzeba już wartość docelową.
Oczywiście, gdybyśmy po prostu przetestowali wszystkie możliwe 3 krotki, rozwiązalibyśmy problem w O (n 3 ) - to jest linia bazowa brutalnej siły. Czy można zrobić lepiej? A jeśli wybierzemy krotki w nieco sprytniejszy sposób?
Najpierw poświęcamy trochę czasu na posortowanie tablicy, co kosztuje nas początkową karę w wysokości O (n log n). Teraz wykonujemy ten algorytm:
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.
}
Algorytm ten działa poprzez umieszczenie trzy wskaźniki, i
, j
, i k
w różnych punktach w tablicy. i
zaczyna się na początku i powoli dociera do końca. k
wskazuje na ostatni element. j
wskazuje, gdzie i
się zaczęło. Iteracyjnie próbujemy zsumować elementy w ich odpowiednich indeksach i za każdym razem, gdy zachodzi jedna z poniższych sytuacji:
- Suma jest dokładnie poprawna! Znaleźliśmy odpowiedź.
- Suma była za mała. Zbliż
j
się do końca, aby wybrać następną największą liczbę.
- Suma była za duża. Przejdź
k
bliżej początku, aby wybrać następną najmniejszą liczbę.
Dla każdego i
wskaźniki j
i k
będą się do siebie stopniowo zbliżać. W końcu mijają się nawzajem i na tym etapie nie musimy próbować niczego innego i
, ponieważ sumowalibyśmy te same elementy, tylko w innej kolejności. Następnie próbujemy następnego i
i powtarzamy.
W końcu albo wyczerpimy przydatne możliwości, albo znajdziemy rozwiązanie. Widać, że jest to O (n 2 ), ponieważ wykonujemy zewnętrzną pętlę O (n) razy, a wewnętrzną pętlę wykonujemy O (n) razy. Można to zrobić pod-kwadratowo, jeśli masz naprawdę ochotę, przedstawiając każdą liczbę całkowitą jako wektor bitowy i wykonując szybką transformatę Fouriera, ale to wykracza poza zakres tej odpowiedzi.
Uwaga: ponieważ jest to pytanie do wywiadu, trochę tutaj oszukałem: ten algorytm pozwala na wielokrotny wybór tego samego elementu. Oznacza to, że (-1, -1, 2) byłoby prawidłowym rozwiązaniem, podobnie jak (0, 0, 0). Znajduje również tylko dokładne odpowiedzi, a nie najbliższą odpowiedź, jak wspomina tytuł. Jako ćwiczenie dla czytelnika, pozwolę ci dowiedzieć się, jak sprawić, by działało tylko z różnymi elementami (ale to bardzo prosta zmiana) i dokładnymi odpowiedziami (co jest również prostą zmianą).