在Java中,我应该如何找到数组元素与特定值K的最接近(或相等)可能的总和?
例如,对于数组{19,23,41,5,40,36}和K = 44,最接近的总和是23 + 19 = 42。 几个小时以来我一直在努力奋斗;我对动态编程几乎一无所知。顺便说一下,数组只包含正数。
答案 0 :(得分:12)
您通常会使用动态编程来解决此类问题。但是,这基本上归结为保留一组可能的总和并逐个添加输入值,如下面的代码所示,并具有相同的渐近运行时间: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 / setters来保持示例简洁),这是相对简单的:
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);
}
答案 1 :(得分:1)
您可以将其视为所有可能n-choose-k
的{{1}}问题,因此复杂性为is exponential。
k
的数字。对于K
,该集应包含i
个数字。为了实现这一点,对于每个i=1; i<=N; i++
,只需获取数组中所有i
combinations个数字。n-choose-i
变量,其中包含到目前为止找到的最佳数字集及其总和。finalResult
进行比较,并在必要时进行更新。它让我想起了Knapsack problem,所以你可能想看看它。
答案 2 :(得分:0)
我会说首先排序数组。然后你的例子是:arr = {5,19,23,36,40,41}。 然后: 1)取arr [0]和arr [i],其中i = arr.Size。如果总和低于K,则将其求和并记录总和与K之间的差值。 2)如果总和> K,做第1步,但是使用arr [i-1]代替arr [i],因为我们想要降低总和。 如果总和&lt; K,执行步骤1,但不使用arr [0],而是使用arr [1],因为我们想增加总和。 通过增加或减少索引来继续重复步骤2,直到两个元素的索引相等。 然后,我们知道导致和与K之间的最小差异的元素对。
----------------编辑解决方案中的任意数量的元素----------------
我相信你可能需要一棵树。这就是我的想法:
1)选择一个数字作为您的顶级节点。
2)对于集合中的每个数字,创建一个子节点,并为每个创建的分支创建, 计算那个分支的总和。
3)如果总和小于K,我们再次分支,为集合中的所有元素创建子节点。如果总和大于K,我们停止,保持总和与K之间的差值(如果总和 使用不同的topnodes执行步骤1-3。
答案 3 :(得分:0)
private int closestSum(int[] a, int num){
int k=a.length-1;
int sum=0;
Arrays.sort(a);
while(a[k]>num){
k--;
}
for(int i=0;i<k;i++){
for(int j=i+1;j<=k;j++){
if(a[i]+a[j]<=num && a[i]+a[j]>sum)
sum = a[i]+a[j];
}
}
return sum;
}