计算组合的有效算法,数组的重复加起来等于给定的总和

Posted

技术标签:

【中文标题】计算组合的有效算法,数组的重复加起来等于给定的总和【英文标题】:Efficient algorithm to compute combinations with repetitions of an array adding up to given sum 【发布时间】:2020-06-19 22:25:26 【问题描述】:

所以在个人 C++ 项目中,我遇到了一个问题。我将其改写如下:

给定一个由 n 个元素组成的数组(例如 [1, 3, 5],其中 n = 3 个元素),其中第 i 个位置的数字表示有多少第 i 个索引处的数字可以取的可能值(例如,这里第一个元素可以取 1 个值,即 0;第二个元素可以取 0、1、2 中的 3 个值;第三个元素可以从 0,1,2,3,4) 中取 5 个值。

我需要列出所有可能的长度为 n 且总和小于或等于给定数字 k 的数组。 这是一个例子:

输入 1

输入数组 = [2,2]; k = 2

输出 1

[0,0], [0,1], [1,0], [1,1]

还有,例如:

输入 2

输入数组 = [2,2]; k = 1

输出 2

[0,0], [0,1], [1,0]

问题

我编写了一个简单的递归和简单的迭代解决方案,它会枚举所有数组,只保留总和小于 k 的数组。这些问题在于,对于 n 很大且 k = 1 的情况,我的代码需要很长时间才能运行,因为它会枚举所有情况并保留一些情况。

我看不到任何重叠的子问题,所以我觉得 DP 和 memoization 不适用。我怎样才能为此编写所需的 C++ 代码?

这是我的迭代版本代码:

// enumerates all arrays which sum up to k

vector<vector<int> > count_all_arrays(vector<int> input_array, int k)

    vector<vector<int> > arr;
    int n = (int)input_array.size();

    // make auxilliary array with elements

    for(int i = 0; i < n; i++)
        vector<int> temp(input_array[i]);
        std::iota(temp.begin(), temp.end(), 0);
        arr.push_back(temp);
    

    // computes combinations

    vector<int> temp(n);
    vector<vector<int> > answers;
    vector<int> indices(n, 0);
    int next;

    while(1) 
        temp.clear();
        for (int i = 0; i < n; i++) 
            temp.push_back(arr[i][indices[i]]);  
        long long int total = accumulate(temp.begin(), temp.end(), 0);
        if(total <= k)
            answers.push_back(temp);
        next = n - 1; 
        while (next >= 0 &&  
              (indices[next] + 1 >= (int)arr[next].size())) 
            next--; 
        if (next < 0) 
            break; 
        indices[next]++; 
        for (int i = next + 1; i < n; i++) 
            indices[i] = 0; 
    
    return answers;

【问题讨论】:

你能发布递归版本吗?这可能会更容易优化。 DP 似乎适用。考虑 k=14 的 [6,6,6,6]。部分解决方案 [4,3,x,x] 和 [2,5,x,x] 都导致 k=7 的子问题 [6,6]。 @user3386109 如果 OP 只需要计算此类数组的总数,那么您可以 DP,但如果他们需要实际枚举所有数组,那么使用递归和一些简单的检查可能是最简单的防止生成无效数组。 @BessieTheCow 我丢失了大约 200 个文件中的递归版本,但大致如下:geeksforgeeks.org/combinations-with-repetitions @user3386109 我确实需要明确列出所有这样的数组,是的:( 【参考方案1】:

这是一个非常简单的递归任务:

#include <bits/stdc++.h>    
using namespace std;

int arr[] = 2, 2;
int n = 2;
int k = 2;

void gen(int pos, int sum, string s)
    if(pos == n)
        cout<<"["<<s<<" ]"<<endl;
        return;
    
    for(int i = 0; i < arr[pos]; i++)
        if(sum + i > k) return;
        gen(pos + 1, sum + i, s + " " + to_string(i));
    


int main()
    gen(0, 0, "");
    return 0;

只需为数组的每个槽生成所有可能性,对于每个选择,将总和用于评估下一个槽。

n 很大且k = 1 时,很自然需要 O(n),因为您将拥有:

[0, 0, 0, ..., 0, 0, 1]
[0, 0, 0, ..., 0, 1, 0]
[0, 0, 0, ..., 1, 0, 0]
          ...
[0, 0, 1, ..., 0, 0, 0]
[0, 1, 0, ..., 0, 0, 0]
[1, 0, 0, ..., 0, 0, 0]

【讨论】:

我自己想通了,我做了一些 hacky 引用传递以使我的递归代码版本更快。不过,这也有效,感谢您花时间去做!【参考方案2】:

你应该使用 dp 让它在任何情况下都快。使用 dp[i][j] 表示您使用第一个 j 元素创建小于或等于 i 的总和的次数。

dp[i][j] = dp[

for (int l = 0; l <= i; l++) 
    dp[i][j] += dp[l][j] + min(i-l+1, input[j])

结果是dp[k,n]

【讨论】:

以上是关于计算组合的有效算法,数组的重复加起来等于给定的总和的主要内容,如果未能解决你的问题,请参考以下文章

获取加起来等于给定数字的所有可能总和

查找等于给定总和的数组元素[关闭]

改进子集解决方案

组合总和--力扣

Leetcode 39 组合总和(回溯算法解题)

LeetCode[39]: 组合总和