在Java中,我应该如何找到数组元素最接近(或等于)特定值K的可能总和?
例如,对于数组{19,23,41,5,40,36}和K=44,最接近的可能总和是23+19=42。 我已经苦苦思考了几个小时;我对动态规划知之甚少。此外,数组仅包含正数。
在Java中,我应该如何找到数组元素最接近(或等于)特定值K的可能总和?
例如,对于数组{19,23,41,5,40,36}和K=44,最接近的可能总和是23+19=42。 我已经苦苦思考了几个小时;我对动态规划知之甚少。此外,数组仅包含正数。
通常在这种问题中,您会使用动态规划。但是,这基本上归结为保持可能的总和集,并逐个添加输入值,如下所示的代码,并具有相同的渐近运行时间: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);
}
upperBound
,它使得 sums
的大小保持在 K
的线性范围内。 - Vincent van der Weeleprivate 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;
}
我会先对数组进行排序。然后你的例子就是: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],因为我们希望降低总和。 如果总和< K,请执行步骤1,但使用arr [1]代替arr [0],因为我们希望增加总和。 通过增加或减少索引重复步骤2,直到两个元素的索引相等。 然后,我们就知道了产生总和与K之间差值最小的一对元素。
----------------适用于任意数量元素的解决方案的编辑----------------
我认为你可能需要一棵树。这是我的想法:
1)选择一个数字作为顶部节点。
2)对于集合中的每个数字,创建一个子节点,并为每个创建的分支计算该分支的总和。
3) 如果总和小于K,我们再次分支,为集合中的所有元素创建子节点。如果总和大于K,我们停止,并保留总和与K之间的差值(如果总和小于K)。如果我们找到一个具有更好总和的分支,则保留该分支。重复此过程,直到所有分支都完成分支。