除了蛮力搜索之外,还有什么有效的算法可以找到三个整数吗?
是的;我们可以在 O(n2) 时间内解决这个问题!首先,考虑到您的问题P 可以用稍微不同的方式等效地表述,从而消除对“目标值”的需要:
原始问题P: 给定一个数组A 的n 整数和一个目标值S,是否存在来自A 的三元组和@ 987654327@?
修改问题P': 给定一个数组A n 整数,是否存在来自A 的三元组总和为零?
请注意,您可以通过从A 中的每个元素中减去您的 S/3 来从 P 中解决此版本的问题 P',但现在您不再需要目标值了。
显然,如果我们简单地测试所有可能的 3 元组,我们将在 O(n3) 内解决问题——这就是蛮力基线。有没有可能做得更好?如果我们以更智能的方式选择元组会怎样?
首先,我们花费一些时间对数组进行排序,这使我们的初始损失为 O(n log n)。现在我们执行这个算法:
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.
}
该算法通过将三个指针i、j 和k 放置在数组中的不同点来工作。 i 从头开始,慢慢地走到最后。 k 指向最后一个元素。 j 指向 i 开始的位置。我们反复尝试对各自索引处的元素求和,每次发生以下情况时:
- 总和完全正确!我们找到了答案。
- 总和太小了。将
j 移近末尾以选择下一个最大的数字。
- 总和太大了。将
k 移近开头以选择下一个最小的数字。
对于每个i,j和k的指针会逐渐靠近。最终它们会互相通过,此时我们不需要为i 尝试其他任何东西,因为我们会以不同的顺序对相同的元素求和。在那之后,我们尝试下一个i 并重复。
最终,我们要么穷尽有用的可能性,要么找到解决方案。您可以看到这是 O(n2),因为我们执行了外循环 O(n) 次,而我们执行了内循环 O(n) 次。如果您真的很喜欢,可以通过将每个整数表示为位向量并执行快速傅立叶变换,以二次方的方式执行此操作,但这超出了此答案的范围。
注意:因为这是一道面试题,所以我这里有点作弊:这个算法允许多次选择同一个元素。也就是说,(-1, -1, 2) 和 (0, 0, 0) 一样是有效的解决方案。如标题所述,它还只找到 exact 答案,而不是最接近的答案。作为对读者的练习,我将让您弄清楚如何使其仅使用不同的元素(但这是一个非常简单的更改)和确切的答案(这也是一个简单的更改)。