如何找到数组元素与特定值最接近的总和?

时间:2013-04-15 18:26:19

标签: java arrays algorithm dynamic-programming

在Java中,我应该如何找到数组元素与特定值K的最接近(或相等)可能的总和?

例如,对于数组{19,23,41,5,40,36}和K = 44,最接近的总和是23 + 19 = 42。 几个小时以来我一直在努力奋斗;我对动态编程几乎一无所知。顺便说一下,数组只包含正数。

4 个答案:

答案 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

  1. 查找一组总和最多为k的数字。对于K,该集应包含i个数字。为了实现这一点,对于每个i=1; i<=N; i++,只需获取数组中所有i combinations个数字。
  2. 保留一个n-choose-i变量,其中包含到目前为止找到的最佳数字集及其总和。
  3. 将步骤1的每个子结果与finalResult进行比较,并在必要时进行更新。
  4. 它让我想起了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;
}