这个问题有很多解决方法:
方法 1 - 排序 - O(nlogn)
我想最简单的方法是按降序排序,然后取出总和至少为 W 的前 K 个元素。时间复杂度将是 O(nlogn)。
方法 2 - 最大堆 - O(n + klogn)
另一种方法是使用最大堆。
创建堆将占用O(n),然后提取元素,直到总和至少为W。每次提取将占用O(logn),因此总时间复杂度为O(klogn),其中k是元素的数量我们必须从堆中提取。
方法 3 - 使用最小堆 - O(nlogk)
添加JimMischel 在下面的 cmets 中建议的此方法。
使用列表中的第一个 k 元素创建一个最小堆,总和至少为 W。然后,遍历剩余的元素,如果它大于它们之间的最小值(堆顶),则替换它们。
在这一点上,可能是我们实际上需要更多的元素才能到达W,所以我们将只提取最小值,直到达到我们的极限。在实践中,取决于
find_min_set(A,W)
currentW = 0
heap H //Create empty heap
for each Elem in A
if (currentW < W)
H.add(Elem)
currentW += Elem
else if (Elem > H.top())
currentW += (Elem-H.top())
H.pop()
H.add(Elem)
while (currentW-H.top() > W)
currentW -= H.top()
H.pop()
根据k 和n 之间的关系,这种方法在实践中可能会更快。见when theory meets practice。
方法 4 - O(n)
我能想到的最佳方法是使用某种quickselect,同时跟踪总权重并始终以中位数为轴进行分区。
首先,让我们定义几件事:
sum(A) - 数组A中所有元素的总和。
num(A) - 数组A中的元素个数。
med(A) - 数组@987654342的中位数@。
find_min_set(A,W,T)
//partition A
//L contains all the elements of A that are less than med(A)
//R contains all the elements of A that are greater or equal to med(A)
L, R = partition(A,med(A))
if (sum(R)==W)
return T+num(R)
if (sum(R) > W)
return find_min_set(R,W,T)
if (sum(R) < W)
return find_min_set(L,W-sum(R),num(R)+T)
由find_min_set(A,W,0)调用此方法。
运行时复杂度:
- 查找中位数为
O(n)。
- 分区是
O(n)。
- 每个递归调用都占用数组大小的一半。
- 总结起来,我们得到一个跟随关系:
T(n) = T(n/2) + O(n),这与 quickselect = O(n) 的平均情况相同。
注意:当所有值都是唯一的时,最坏情况和平均复杂度确实是O(n)。对于可能的重复值,平均复杂度仍然是O(n),但最坏的情况是O(nlogn),使用Median of medians 方法选择枢轴。