【问题标题】:How do I find the closest possible sum of an Array's elements to a particular value?如何找到最接近特定值的数组元素总和?
【发布时间】:2013-04-07 23:34:13
【问题描述】:

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

例如,对于数组 {19,23,41,5,40,36} 和 K=44,最接近的可能和是 23+19=42。 我已经为此苦苦挣扎了几个小时;我对动态编程几乎一无所知。顺便说一句,数组只包含正数。

【问题讨论】:

  • 这是一个公平的问题,但可能更适合这里:math.stackexchange.com
  • 5 + 40 不是最接近的总和吗?
  • 我的意思是总和小于或等于 K
  • 你可以加起来的数字有限制吗?例如,您可以每次求和仅使用两个元素,还是也可以对所有元素使用求和?

标签: java arrays algorithm dynamic-programming


【解决方案1】:

我会说首先对数组进行排序。那么您的示例将是:arr = {5, 19, 23, 36, 40, 41}。 然后: 1) 取 arr[0] 和 arr[i],其中 i = arr.Size。求和并记录和与 K 之间的差,如果和小于 K。 2) 如果 sum > K,执行第 1 步,但不要使用 arr[i],而是使用 arr[i-1],因为我们想降低总和。 如果 sum

----------------针对解决方案中任意数量的元素进行了编辑----------------

我相信你可能需要一棵树。这是我的想法:

1) 选择一个数字作为您的顶部节点。

2) 对于集合中的每个数字,创建一个子节点,对于创建的每个分支, 计算那个分支的总和。

3) 如果总和小于 K,我们再次分支,为集合中的所有元素创建子节点。如果总和大于 K,我们停止,保持总和与 K 之间的差(如果总和

使用不同的顶部节点执行步骤 1-3。

【讨论】:

  • 如果我理解正确,这并不能正确解决他的问题;以 [1, 2, 10] 搜索 13 为例。
  • @Calpis,但是超过两个数字参与结果呢?
  • 抱歉,我没有意识到这是一个任意的数字。让我重新考虑一下
【解决方案2】:

对于此类问题,您通常会使用动态规划。但是,这基本上归结为保留一组可能的总和并逐个添加输入值,如以下代码所示,并且具有相同的渐近运行时间: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);
}

【讨论】:

  • 我阅读了您的解决方案并尝试理解,也许我的问题很愚蠢,但我可以看到您在最终解决方案中添加数字的位置,我没有看到是你排除一些最终解决方案的地方。我希望,在某些时候,您应该意识到应该排除包含的数字以产生更好的解决方案,不是吗?
  • @zafeiris.m 我只跟踪总和,而不是它们组合的值。基本上,在外循环的迭代i 之后(在inputs 上),集合sums 包含所有 可以用第一个i 输入值得出的总和。为了确保该集合的大小不会呈指数增长,我添加了upperBound,这确保sums 最多包含2*K 值。
  • 但是如果最好的解决方案不包括第一个 i 元素,而是第一个、第四个和第六个呢?
  • @Heuster 谢谢,我现在可以看到了,很好的解决方案!但我不明白的是最坏情况的复杂性。在您的评论示例中(并且在大多数元素将包含在结果中的任何示例中),我看到结果集每次都加倍,导致 exponential 复杂性,不是吗?如果我错了,请告诉我。 更新:空间复杂度也是指数级的,因为你保留了每个子结果,对吧?
  • @zafeiris.m 我添加了关于运行时间的注释。诀窍在于upperBound,它使sums 的大小在K 中保持线性。
【解决方案3】:

对于所有可能的k,您可以将其视为n-choose-k 问题,因此复杂性is exponential

  1. 找出一组数的总和最多为K。该集合应包括i 数字,用于i=1; i&lt;=N; i++。要实现这一点,对于每个 i,只需获取数组中所有数字的 n-choose-i combinations
  2. 保留一个 finalResult 变量,其中包含迄今为止找到的最佳数字集及其总和。
  3. 将第 1 步的每个子结果与finalResult 进行比较,并在必要时进行更新。

它让我想起了Knapsack problem,所以你可能想看看它。

【讨论】:

    【解决方案4】:
    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;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-03-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多