Existe-t-il un algorithme efficace autre que la recherche par force brute pour trouver les trois entiers?
Oui; nous pouvons résoudre cela en temps O (n 2 )! Tout d'abord, considérez que votre problème P
peut être formulé de manière équivalente d'une manière légèrement différente qui élimine le besoin d'une «valeur cible»:
problème d'origine P
: étant donné un tableau A
d' n
entiers et une valeur cible S
, existe-t-il un 3-tuple de A
cette somme à S
?
problème modifié P'
: étant donné un tableau A
d' n
entiers, existe-t-il un 3-tuple de A
cette somme à zéro?
Notez que vous pouvez passer de cette version du problème P'
de P
en soustrayant votre S / 3 de chaque élément A
, mais maintenant vous n'avez pas besoin de la valeur cible plus.
Clairement, si nous testons simplement tous les 3 tuples possibles, nous résoudrons le problème en O (n 3 ) - c'est la ligne de base de la force brute. Est-il possible de faire mieux? Et si nous choisissions les tuples d'une manière un peu plus intelligente?
Tout d'abord, nous investissons du temps pour trier le tableau, ce qui nous coûte une pénalité initiale de O (n log n). Maintenant, nous exécutons cet algorithme:
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.
}
Cet algorithme fonctionne en plaçant trois pointeurs, i
, j
et k
à divers points du tableau. i
commence par le début et avance lentement jusqu'à la fin. k
pointe vers le tout dernier élément. j
indique où i
a commencé. Nous essayons itérativement de faire la somme des éléments à leurs indices respectifs, et à chaque fois l'un des événements suivants se produit:
- La somme est exactement la bonne! Nous avons trouvé la réponse.
- La somme était trop petite. Rapprochez-vous
j
de la fin pour sélectionner le prochain plus grand nombre.
- La somme était trop importante. Rapprochez-
k
vous du début pour sélectionner le plus petit nombre suivant.
Pour chacun i
, les pointeurs de j
et k
se rapprochent progressivement les uns des autres. Finalement, ils se croiseront, et à ce stade, nous n'avons pas besoin d'essayer autre chose pour cela i
, puisque nous additionnerions les mêmes éléments, juste dans un ordre différent. Après ce point, nous essayons le suivant i
et répétons.
Finalement, soit nous épuiserons les possibilités utiles, soit nous trouverons la solution. Vous pouvez voir que c'est O (n 2 ) puisque nous exécutons la boucle externe O (n) fois et nous exécutons la boucle interne O (n) fois. Il est possible de le faire de manière sous-quadratique si vous avez vraiment envie, en représentant chaque entier comme un vecteur de bits et en effectuant une transformation de Fourier rapide, mais cela dépasse le cadre de cette réponse.
Remarque: Comme il s'agit d'une question d'entretien, j'ai un peu triché ici: cet algorithme permet de sélectionner plusieurs fois le même élément. Autrement dit, (-1, -1, 2) serait une solution valide, comme le ferait (0, 0, 0). Il ne trouve également que les réponses exactes , pas la réponse la plus proche, comme l'indique le titre. En guise d'exercice pour le lecteur, je vais vous laisser comprendre comment le faire fonctionner avec des éléments distincts uniquement (mais c'est un changement très simple) et des réponses exactes (ce qui est également un changement simple).