Existe algum algoritmo eficiente além da pesquisa de força bruta para encontrar os três números inteiros?
Sim; podemos resolver isso em O (n 2 ) tempo! Primeiro, considere que seu problema Ppode ser formulado de forma equivalente de uma maneira ligeiramente diferente que elimina a necessidade de um "valor-alvo":
problema original P: Dada uma matriz Ade nnúmeros inteiros e um valor de destino S, existe uma tupla de 3 Asomas para S?
problema modificado P': Dada uma matriz Ade nnúmeros inteiros, existe uma tupla de 3 Aque soma a zero?
Observe que você pode ir a partir desta versão do problema P'de Psubtraindo o seu S / 3 de cada elemento A, mas agora você não precisa o valor-alvo mais.
Claramente, se simplesmente testássemos todas as três tuplas possíveis, resolveríamos o problema em O (n 3 ) - essa é a linha de base da força bruta. É possível fazer melhor? E se escolhermos as tuplas de uma maneira um pouco mais inteligente?
Primeiro, investimos algum tempo para classificar a matriz, o que nos custa uma penalidade inicial de O (n log n). Agora, executamos 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 colocando três ponteiros, i, j, e kem vários pontos da matriz. icomeça no começo e lentamente segue seu caminho até o fim. kaponta para o último elemento. japonta para onde icomeçou. Tentamos iterativamente somar os elementos em seus respectivos índices e cada vez que acontece um dos seguintes:
- A soma é exatamente correta! Nós encontramos a resposta.
- A soma era muito pequena. Aproxime-
jse do final para selecionar o próximo maior número.
- A soma era muito grande. Aproxime-
kse do início para selecionar o próximo número menor.
Para cada um i, os ponteiros je kgradualmente se aproximam um do outro. Eventualmente, eles passarão um pelo outro e, nesse ponto, não precisamos tentar mais nada para isso i, pois estaríamos somando os mesmos elementos, apenas em uma ordem diferente. Depois desse ponto, tentamos o próximo ie repetimos.
Eventualmente, esgotaremos as possibilidades úteis ou encontraremos a solução. Você pode ver que esse é O (n 2 ), pois executamos o loop externo O (n) vezes e executamos o loop interno O (n) vezes. É possível fazer isso sub-quadraticamente se você realmente gosta, representando cada número inteiro como um vetor de bits e realizando uma transformação rápida de Fourier, mas isso está além do escopo desta resposta.
Nota: Por se tratar de uma pergunta de entrevista, eu trapacei um pouco aqui: esse algoritmo permite a seleção do mesmo elemento várias vezes. Ou seja, (-1, -1, 2) seria uma solução válida, como seria (0, 0, 0). Ele também encontra apenas as respostas exatas , e não a mais próxima, como o título menciona. Como exercício para o leitor, deixarei você descobrir como fazê-lo funcionar apenas com elementos distintos (mas é uma mudança muito simples) e respostas exatas (que também são uma mudança simples).