对于此类问题,您通常会使用动态规划。但是,这基本上归结为保留一组可能的总和并逐个添加输入值,如以下代码所示,并且具有相同的渐近运行时间:O(n K),其中n 是您输入的大小数组,K 是目标值。
然而,下面版本中的常量可能更大,但我认为代码比动态编程版本更容易理解。
public class Test {
public static void main(String[] args) {
int K = 44;
List<Integer> inputs = Arrays.asList(19,23,41,5,40,36);
int opt = 0; // optimal solution so far
Set<Integer> sums = new HashSet<>();
sums.add(opt);
// loop over all input values
for (Integer input : inputs) {
Set<Integer> 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 的上限,所以sums 中的所有值都在[0, K] 范围内。由于sums是一个集合,它最多包含K+1元素。
内部循环内的所有计算都需要固定时间,因此整个循环需要O(K)。集合newSums也最多包含K+1元素,出于同样的原因,所以最后的addAll也是O(K)。
结束:外部循环执行n 次。循环体采用O(K)。因此,算法运行在O(n K)。
编辑 2
关于如何找到导致最优和的元素的请求:
除了跟踪单个整数(子列表的总和)之外,您还应该跟踪子列表本身。如果您创建一个新类型,这相对简单(没有 getter/setter 以保持示例简洁):
public class SubList {
public int size;
public List<Integer> subList;
public SubList() {
this(0, new ArrayList<>());
}
public SubList(int size, List<Integer> subList) {
this.size = size;
this.subList = subList;
}
}
现在初始化变成:
SubList opt = new SubList();
Set<SubList> sums = new HashSet<>();
sums.add(opt);
sums 上的内部循环也需要一些小的修改:
for (Integer input : inputs) {
Set<SubList> newSums = new HashSet<>();
// loop over all sums so far
for (SubList sum : sums) {
List<Integer> 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);
}