¿Existe algún algoritmo eficiente que no sea la búsqueda de fuerza bruta para encontrar los tres enteros?
Sí; ¡podemos resolver esto en O (n 2 ) tiempo! Primero, considere que su problema P
puede expresarse de manera equivalente de una manera ligeramente diferente que elimine la necesidad de un "valor objetivo":
problema original P
: Dada una matriz A
de n
enteros y un valor objetivo S
, ¿existe una tupla de 3 a partir de A
esa suma S
?
problema modificado P'
: Dada una matriz A
de n
enteros, ¿existe una tupla de 3 de A
esas sumas a cero?
Tenga en cuenta que se puede pasar de esta versión del problema P'
del P
restando su S / 3 de cada elemento A
, pero ahora no es necesario el valor objetivo más.
Claramente, si simplemente probamos todas las 3 tuplas posibles, resolveríamos el problema en O (n 3 ), esa es la línea base de fuerza bruta. ¿Es posible hacerlo mejor? ¿Qué pasa si elegimos las tuplas de una manera algo más inteligente?
Primero, invertimos algo de tiempo para ordenar la matriz, lo que nos cuesta una penalización inicial de O (n log n). Ahora ejecutamos este 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.
}
Este algoritmo funciona mediante la colocación de tres puntos, i
, j
, y k
en varios puntos de la matriz. i
comienza al principio y avanza lentamente hasta el final. k
apunta al último elemento. j
señala dónde i
ha comenzado. Intentamos sumar iterativamente los elementos en sus respectivos índices, y cada vez que ocurre uno de los siguientes:
- ¡La suma es exactamente la correcta! Hemos encontrado la respuesta.
- La suma era demasiado pequeña. Mover
j
más cerca del final para seleccionar el siguiente número más grande.
- La suma fue demasiado grande. Mover
k
más cerca del comienzo para seleccionar el siguiente número más pequeño.
Para cada uno i
, los punteros de j
y k
gradualmente se acercarán entre sí. Eventualmente se pasarán entre sí, y en ese punto no necesitamos probar nada más para eso i
, ya que estaríamos sumando los mismos elementos, solo en un orden diferente. Después de ese punto, intentamos el siguiente i
y repetimos.
Finalmente, agotaremos las posibilidades útiles o encontraremos la solución. Puede ver que esto es O (n 2 ) ya que ejecutamos el bucle externo O (n) veces y ejecutamos el bucle interno O (n) veces. Es posible hacer esto subcuadráticamente si te apetece, representando cada número entero como un vector de bits y realizando una transformación rápida de Fourier, pero eso está más allá del alcance de esta respuesta.
Nota: Debido a que esta es una pregunta de entrevista, he engañado un poco aquí: este algoritmo permite la selección del mismo elemento varias veces. Es decir, (-1, -1, 2) sería una solución válida, como lo sería (0, 0, 0). También encuentra solo las respuestas exactas , no la respuesta más cercana, como menciona el título. Como ejercicio para el lector, te dejaré descubrir cómo hacer que funcione solo con elementos distintos (pero es un cambio muy simple) y respuestas exactas (que también es un cambio simple).