输出所有组合的硬币更改算法是否仍可通过DP解决?

时间:2015-10-26 07:08:03

标签: c++ algorithm

例如,总金额应为5,我的硬币值为1和2.然后有3种组合方式:

1 1 1 1 1
1 1 1 2
1 2 2

我看过一些关于如何使用动态编程或递归计算组合总数的帖子,但我想输出所有组合,如上面的例子。我在下面提出了一个递归解决方案。

它基本上是一个回溯算法,我首先从最小的硬币开始并尝试达到总金额,然后我删除一些硬币并尝试使用第二小硬币......你可以在{{3}下面运行我的代码}

我的代码中总金额为10,可用硬币值为1,2,5。

#include <iostream>
#include <stdlib.h>
#include <iomanip>
#include <cmath>
#include <vector>

using namespace std;


vector<vector<int>> res;
vector<int> values;
int total = 0;

void helper(vector<int>& curCoins, int current, int i){

    int old = current;

    if(i==values.size())
        return;

    int val = values[i];

    while(current<total){
        current += val;
        curCoins.push_back(val);
    }

    if(current==total){
        res.push_back(curCoins);
    }


    while (current>old) {
        current -= val;
        curCoins.pop_back();

        if (current>=0) {
            helper(curCoins, current, i+1);
        }
    }


}


int main(int argc, const char * argv[]) {       

    total = 10;
    values = {1,2,5};
    vector<int> chosenCoins;

    helper(chosenCoins, 0, 0);

    cout<<"number of combinations: "<<res.size()<<endl;
    for (int i=0; i<res.size(); i++) {
        for (int j=0; j<res[i].size(); j++) {
            if(j!=0)
                cout<<" ";
            cout<<res[i][j];
        }
        cout<<endl;
    }

    return 0;
}

是否有更好的解决方案来输出此问题的所有组合?动态编程?

编辑:

我的问题是这个问题是否可以使用动态编程解决?

感谢您的帮助。我在这里实现了DP版本:http://cpp.sh/

5 个答案:

答案 0 :(得分:1)

通过动态编程,穷举搜索不太可能“更好”,但这是一个可能的解决方案:

从组合字符串的2d数组开始,arr [value] [index]其中value是硬币的总价值。设X为目标值;

从arr [0] [0] =“”开始; 对于每个硬币面额n,从i = 0到X-n,您将所有字符串从arr [i]复制到arr [i + n]并将n附加到每个字符串。

例如n = 5,你最终会得到 arr [0] [0] =“”,arr [5] [0] =“5”和arr [10] [0] =“5 5”

希望有道理。典型的DP只是计数而不是字符串(你也可以用int向量替换字符串以保持计数)

答案 1 :(得分:1)

DP解决方案:

我们有

{solutions(n)} = Union ({solutions(n - 1) + coin1},
                        {solutions(n - 2) + coin2},
                        {solutions(n - 5) + coin5})

所以在代码中:

using combi_set = std::set<std::array<int, 3u>>;

void append(combi_set& res, const combi_set& prev, const std::array<int, 3u>& values)
{
    for (const auto& p : prev) {
        res.insert({{{p[0] + values[0], p[1] + values[1], p[2] + values[2]}}});   
    }
}

combi_set computeCombi(int total)
{
    std::vector<combi_set> combis(total + 1);

    combis[0].insert({{{0, 0, 0}}});
    for (int i = 1; i <= total; ++i) {
        append(combis[i], combis[i - 1], {{1, 0, 0}});
        if (i - 2 >= 0) { append(combis[i], combis[i - 2], {{0, 1, 0}}); }
        if (i - 5 >= 0) { append(combis[i], combis[i - 5], {{0, 0, 1}}); }
    }
    return combis[total];
}

Live Demo

答案 2 :(得分:0)

假设您有O(K)您期望的输出总大小(所有组合中的硬币总数)。显然,如果你真的需要输出所有它们,你就不能拥有比K更快的解决方案。由于O(N*S+K)可能非常大,这将是一个非常长的运行时间,在最坏的情况下,您将从动态编程中获得很少的利润。

但是,您仍然可以比直接的递归解决方案做得更好。也就是说,您可以在N中运行解决方案,其中S是您拥有的硬币数量,K是总和。对于最差的K,这并不比直接解决方案好,但如果O(N*S+K)不是那么大,那么你的运行速度会比递归解决方案快。

current解决方案可以相对简单地编码。首先,您运行标准DP解决方案,以找出每个总和i和每个current总和i是否可以由第一个current硬币类型组成。您尚未计算所有解决方案,只需了解每个ivoid helper(vector<int>& curCoins, int current, int i){ if (!solutionExists[current, i]) return; // then your code goes 是否至少存在一种解决方案。然后,您编写一个类似于您已编写的递归函数,但在尝试每个组合之前,您使用DP表检查是否值得尝试,即是否至少存在一个解决方案。类似的东西:

O(K)

这样递归树的每个分支都将完成寻找解决方案,因此总递归树大小为O(N*S+K),总运行时间为{{1 }}

另请注意,只有在您确实需要输出所有组合时,所有这一切才有价值。如果您需要对所获得的组合执行其他操作,则很可能您实际上并不需要所有组合,您可以为此调整DP解决方案。例如,如果您只想打印m - 所有解决方案,可以在O(N*S)中完成。

答案 3 :(得分:0)

您只需要对数据结构进行两次传递(只要您拥有相对较少数量的硬币,哈希表就能正常运行)。

第一个找到的所有唯一总和小于所需总数(实际上你可能停在所需总数的1/2)并记录最简单的方法(所需的最少增加)以获得该总和。这基本上与DP相同。

然后第二遍以期望的总数开始,然后向后通过数据输出所有可以生成总数的方式。

这最终成为彼得所暗示的两阶段方法。

答案 4 :(得分:0)

使用纯递归穷举技术(以下代码),数量{1、2、5}和N = 10的非唯一有效组合的实际数量为128。我的问题是,可以通过记忆/动态编程来改进详尽的搜索。如果是这样,我如何修改下面的算法以合并此类技术。

public class Recursive {

    static int[] combo = new int[100];
    public static void main(String argv[]) {
        int n = 10;
        int[] amounts = {1, 2, 5};
        ways(n, amounts, combo, 0, 0, 0);
    }

    public static void  ways(int n, int[] amounts, int[] combo, int startIndex, int sum, int index) {
        if(sum == n) {
            printArray(combo, index);
        }

        if(sum > n) {
            return;
        }


        for(int i=0;i<amounts.length;i++) {
            sum = sum + amounts[i];
            combo[index] = amounts[i];
            ways(n, amounts, combo, startIndex, sum, index + 1);
            sum = sum - amounts[i];
        }
    }

    public static void printArray(int[] combo, int index) {
        for(int i=0;i < index; i++) {
            System.out.print(combo[i] + " ");
        }
        System.out.println();
    }
}