首先,值得指出的是,最坏情况下的复杂度不能比O(n^3)好,因为在最坏情况下有O(n^3)三元组,显然每个三元组至少需要恒定时间,以存储/打印它。还有一个非常简单明了的O(n^3)算法。
话虽如此,这就是您如何以复杂的方式做到这一点O(n^2 log n + k),其中k 是答案的大小。 (虽然@saadtaame 声称具有相同的复杂性,但他的估计存在问题,请参阅他的答案下方的 cmets。
首先,让我们修复一个元素,比如a[i]。现在让我们创建一个新数组b,由a 中的所有元素组成,它们的索引都大于i,值大于a[i]。现在问题简化为在b 中找到两个索引j 和k,即j < k 和b[j] < b[k]。
为此,我们可以使用某种排序集,例如 Java 中的 TreeSet。我们将遍历k 的所有可能值,在TreeSet 中维护所有索引小于k 的元素。由于TreeSet 只包含索引小于k(因为我们构建它的方式)和大于i(因为b 只包含这样的元素)的元素,并且被排序,那么每个元素q 中的TreeSet 的值小于b[k] 形成一个答案三元组(a[i], q, b[k])。这是一个伪代码:
for i from 0 to size(a):
b = empty array
for j from i + 1 to size(a):
if a[j] > a[i]:
add a[j] to b
treeSet = new TreeSet
for k from 0 to size(b):
for each element 'e' in the treeSet in sorted order: // (1)
if e >= b[k] or a[i] + e + b[k] > t:
break
add (a[i], e, b[k]) to the answer // (2)
add b[k] to the treeSet // (3)
这里如果我们返回的元素个数小于O(n^2 log n),那么算法的复杂度将是O(n^2 log n)。原因是(2) 行被精确地执行了k 次,因此可以忽略(并且迭代一个treeSet 在元素数量上摊销了线性时间),而内部循环的其余部分:将迭代器初始化为(1) 和在(3) 处向treeSet 添加一个元素最多都是O(log n) 操作。
编辑:这是一个小例子。假设数组是a = [5, 3, 7, 9, 8, 1] 和t = 20。然后i首先指向5,我们把所有从5向右和更大的元素放到b,所以b = [7, 9, 8]。然后k会做3次迭代:
b[k] = 7。此时 treeSet 为空,所以什么也没有发生,7 被添加到 treeSet 中。
b[k] = 9。此时 treeSet 有元素 7。它小于 9,但总和为5 + 7 + 9 > 20,因此我们中断了对 treeSet 的迭代。我们将9 放到treeSet 中,现在集合中包含(7, 9)
b[k] = 8。我们遍历树集。对于元素 7,两个条件都满足 (7 < 8 and 5 + 7 + 8 <= 20),因此将 (5, 7, 8) 添加到答案中。对于元素 9,该元素大于 b[k],因此我们中断。
那么k的循环就结束了。
然后我们将i 向右移动一个元素。 b 的内容会完全一样,上面的三个步骤也差不多,只是第二步的时候答案足够小,所以我们会产生(3, 7, 9) 和(3, 7, 8)。
那么当我们移动到下一个i 时,当a[i] = 7 时,数组b 将只包含两个元素[9, 8],并且不会产生任何答案。
我建议用 Java 编写带有一些调试输出的代码,然后尝试一下以更好地理解它。