您通常会使用动态编程来解决此类问题.但是,这基本上归结为保持一组可能的总和并逐个添加输入值,如下面的代码所示,并具有相同的渐近运行时间:O(n K),其中n是输入的大小数组和K是目标值.
但是,下面版本中的常量可能更大,但我认为代码比动态编程版本更容易理解.
public class Test {
public static void main(String[] args) {
int K = 44;
List inputs = Arrays.asList(19,23,41,5,40,36);
int opt = 0; // optimal solution so far
Set sums = new HashSet<>();
sums.add(opt);
// loop over all input values
for (Integer input : inputs) {
Set newSums = new HashSet<>();
// loop over all sums so far
for (Integer sum : sums) {
int newSum = sum + input;
// ignore too big sums
if (newSum <= K) {
newSums.add(newSum);
// update optimum
if (newSum > opt) {
opt = newSum;
}
}
}
sums.addAll(newSums);
}
System.out.println(opt);
}
}
编辑
关于运行时间的简短说明可能是有用的,因为我只是声称O(n K)没有正当理由.
显然,初始化和打印结果只需要一个恒定的时间,所以我们应该分析双循环.
外循环遍历所有输入,因此它的主体被执行n次.
到目前为止,内循环遍及所有总和,理论上可能是指数.但是,我们使用K的上限,因此所有和的值都在[0,K]范围内.由于sum是一个集合,它最多包含K 1个元素.
内循环内的所有计算都需要恒定的时间,因此总循环需要O(K).由于同样的原因,set newSums还包含最多K 1个元素,因此最后的addAll也需要O(K).
结束:外循环执行n次.循环体取O(K).因此,算法在O(n K)中运行.
编辑2
根据请求如何找到导致最佳总和的元素:
而不是跟踪单个整数 – 子列表的总和 – 您还应该跟踪子列表本身.如果您创建一个新类型(没有getter / setters来保持示例简洁),这是相对简单的:
public class SubList {
public int size;
public List subList;
public SubList() {
this(0, new ArrayList<>());
}
public SubList(int size, List subList) {
this.size = size;
this.subList = subList;
}
}
现在初始化变为:
SubList opt = new SubList();
Set sums = new HashSet<>();
sums.add(opt);
总和上的内循环也需要一些小的调整:
for (Integer input : inputs) {
Set newSums = new HashSet<>();
// loop over all sums so far
for (SubList sum : sums) {
List newSubList = new ArrayList<>(sum.subList);
newSubList.add(input);
SubList newSum = new SubList(sum.size + input, newSubList);
// ignore too big sums
if (newSum.size <= K) {
newSums.add(newSum);
// update optimum
if (newSum.size > opt) {
opt = newSum;
}
}
}
sums.addAll(newSums);
}