【问题标题】:Find all combinations of an array and get top k sum elements查找数组的所有组合并获取前 k 个和元素
【发布时间】:2021-11-14 06:08:40
【问题描述】:

我有一个数字数组,比如说 [1,2,3,1,1000] ,现在我想获取这个数组的所有可能组合并计算它的总和。组合是有效的,使得两个组合具有不同的元素子集。然后将所有的和值按降序排列,得到前k个元素。

示例:

[1,2,3,1,1000]

组合:

删除较早的重复项,例如 (3,1) 匹配较早的 (1,3)。

(), (1), (2), (3), (1), (1000), (1,2), (1,3), (1,1) , (1,1000), (2,3), (2,1), (2,1000), (3,1), (3,1000) , (1,1000), (1,2,3), (1,2,1), (1,2,1000), (1,3,1), (1,3 ,1000), (1,1,1000), (2,3,1), (2,3,1000), (2,1,1000), (3,1,1000), (1,2,3,1), (1,2,3,1000), (1,2,1,1000), (1,3, 1,1000), (2,3,1,1000), (1,2,3,1,1000)

以及相应的总和:

0, 1, 2, 3, 1, 1000, 3, 4, 2, 1001, 5, 3, 1002, 4, 1003, 1001, 6, 4, 1003, 5, 1004, 1002, 6, 1005, 1003, 1004, 7, 1006, 1004, 1005, 1006, 1007

Getting top k=3, sums = 1007, 1006, 1005

So output is [1007, 1006, 1005].

约束:

  • 数组大小 n = 1 到 105
  • 数组元素 -109 到 109
  • k 范围从 1 到 2000

这是我的代码,参考来自here:

static List<Long> printDistSum(int arr[]) {
        List<Long> list = new ArrayList<>();
        int n = arr.length;
        // There are totoal 2^n subsets
        long total = (long) Math.pow(2, n);
        
        // Consider all numbers from 0 to 2^n - 1
        for (int i = 0; i < total; i++) {
            long sum = 0;

            // Consider binary representation of
            // current i to decide which elements
            // to pick.
            for (int j = 0; j < n; j++)
                if ((i & (1 << j)) != 0)
                    sum += arr[j];

            // Print sum of picked elements.
            list.add(sum);
        }
        return list;
    }

此代码适用于小范围的输入,但适用于大范围的输入。如何解决这个程序。

【问题讨论】:

标签: java algorithm


【解决方案1】:

我可能有足够好的解决方案。它的时间复杂度为 O(n * k * log(k))。

首先我们需要计算最大总和 - 所有正值的总和。

接下来我们需要迭代正值,从小到大。对于这些值中的每一个,我们计算新组合的总和(在开始时,我们有一个具有最大总和的组合)。 新组合将不包含给定值,因此我们需要从 sum 中减去它。

最后我们需要迭代负值。这些值不属于上一步的组合,因此我们需要将这些值添加到总和中。

在每次迭代中只需要 k 个最大总和。我使用 PriorityQueue 来存储这些总和。该类使用堆数据结构,因此添加/删除值具有对数时间。

代码:

private static long[] findSums(int[] array, int k) {
    long maxSum = Arrays.stream(array).filter(it -> it >= 0).sum();

    int[] positives = Arrays.stream(array).filter(it -> it >= 0).sorted().toArray();
    int[] negatives = Arrays.stream(array).filter(it -> it < 0).sorted().toArray();
    // sort time complexity is O(n*log(n))

    PriorityQueue<Long> sums = new PriorityQueue<>(k); // priority queue is implemented using heap so adding element has time complexity O(log(n))
    sums.add(maxSum); // we start with max sum - combination of all positive elements

    int previous = Integer.MIN_VALUE;
    Long[] previousAddedSums = {};
    Long[] sumsToIterate;

    // iterate over positive values
    for (int i = 0; i < positives.length; i++) {
        if (positives[i] == previous) {
            sumsToIterate = previousAddedSums;
        } else {
            sumsToIterate = sums.toArray(new Long[sums.size()]);
        }
        previousAddedSums = new Long[sumsToIterate.length];
        for (int j = 0; j < sumsToIterate.length; j++) {
            long newSum = sumsToIterate[j] - positives[i];
            // new sum is calculated - value positives[i] is removed from combination (subtracted from sum of that combination)
            sums.add(newSum);
            previousAddedSums[j] = newSum;
            if (sums.size() > k) {
                sums.poll(); // only first k maximum sums are needed at the moment
            }
        }
        previous = positives[i];
    }

    previous = Integer.MAX_VALUE;
    // iterate over negative values in reverse order
    for (int i = negatives.length - 1; i >= 0; i--) {
        if (negatives[i] == previous) {
            sumsToIterate = previousAddedSums;
        } else {
            sumsToIterate = sums.toArray(new Long[sums.size()]);
        }
        previousAddedSums = new Long[sumsToIterate.length];
        for (int j = 0; j < sumsToIterate.length; j++) {
            long newSum = sumsToIterate[j] + negatives[i]; // value negatives[i] is added to combination (added to sum of that combination)
            sums.add(newSum);
            previousAddedSums[j] = newSum;
            if (sums.size() > k) {
                sums.poll();
            }
        }
        previous = negatives[i];
    }

    long[] result = new long[sums.size()];
    for (int i = sums.size() - 1; i >=0 ; i--) {
        result[i] = sums.poll();
    }
    // get sums from priority queue in proper order
    return result;

    // this whole method has time complexity O(n * k * log(k))
    // k is less than or equal 2000 so it should be good enough ;)
}

演示: https://ideone.com/yf6POI

编辑:我已经修复了我的解决方案。我不是迭代不同的值,而是检查当前值是否与以前的值相同。在这种情况下,我使用在上一步中创建的组合(总和)。这样可以防止创建重复的组合。

如果我解释得不够清楚,我很抱歉。我没有用英语描述算法/数学事物的经验。

【讨论】:

  • 我将 k 更改为 7,您计算出 [1007, 1006, 1005, 1004, 1004, 1003, 1002],缺少第二个 1003。
  • @don'ttalkjustcode 你有权我的解决方案返回错误的答案,但 k = 7 应该是[1007, 1006, 1005, 1005, 1004, 1004, 1003]1007 - [1, 1, 2, 3, 1000]; 1006 - [1, 2, 3, 1000]; 1005 - [2, 3, 1000]; 1005 - [1, 1, 3, 1000]; 1004 - [1, 3, 1000] ; 1004 - [1, 1, 2, 1000]; 1003 - [3, 1000]
  • 对于 [1,1,1] 和 3,它给出了运行时错误。
  • @wLui155 你怎么能用 sum -1 得到这么多组合?组合应该是唯一的,因此您不能有四个 [-1] 组合,只有一个 [-1] 是有效的。在我的解决方案中,0 表示空组合,-1 表示[-1],-2 表示[-1, -1],-3 表示[-1, -1, -1],-3 表示[-3] 等等。
  • @wLui155 对我来说,这似乎是问题中的另一个错误。我认为第二个(1) 应该像其他重复项一样被“--> NOT VALID because match with”丢弃。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-24
  • 2013-01-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多