【问题标题】:Is Coin Change Algorithm That Output All Combinations Still Solvable By DP?输出所有组合的硬币找零算法仍然可以通过 DP 解决吗?
【发布时间】:2015-10-26 07:08:02
【问题描述】:

例如,总金额应该是5,我有1和2的硬币。那么有3种组合方式:

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

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

它基本上是一种回溯算法,我先从最小的硬币开始,然后尝试得到总金额,然后取出一些硬币并尝试使用第二小的硬币......你可以在下面运行我的代码 http://cpp.sh/

在我的代码中,总金额是 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 版本:Coin Change DP Algorithm Print All Combinations

【问题讨论】:

  • 我不认为使用动态编程技术进行详尽的搜索会运行得更快..
  • 您有什么问题要解决,还是只是codereview
  • 您没有在简短示例中显示唯一的 5,这是给出 5 总和的合法方式。
  • @n.m.哦,你是对的,我只是改变了那个
  • @softwarenewbie7331 问题是我不知道如何用 DP 解决它,因为我需要打印所有组合...

标签: c++ algorithm


【解决方案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.

【讨论】:

  • 我这里已经实现了:stackoverflow.com/questions/33353227/… 不过好像有点慢..
  • @Arch1tect,我希望你能帮助我我有一个与你的帖子类似的问题,但是当你说所有组合时,我假设不明显。例如:使用硬币 {1, 2, 5} 和 N = 10,实际上有 128 种方式。这可以使用DP解决吗?我的代码如下
【解决方案2】:

穷举搜索不太可能使用动态编程“更好”,但这里有一个可能的解决方案:

从一个二维组合字符串数组 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 向量替换字符串以保持计数)

【讨论】:

  • 澄清一下;典型的 dp 会快得多,因为对于您迄今为止计算的任意数量的组合,添加只需要计数的总和,而跟踪各个组合将迫使您对每个组合进行某种复制计数器。
【解决方案3】:

假设您有K 您期望的输出总大小(所有组合中的硬币总数)。显然,如果您确实需要输出所有这些解决方案,那么您不可能有比O(K) 运行得更快的解决方案。由于K 可能非常大,这将是一个非常长的运行时间,最坏的情况是您从动态规划中获得的收益很少。

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

这个O(N*S+K) 解决方案可以相对简单地编码。首先,您运行标准 DP 解决方案来找出每个总和 current 和每个 i 总和 current 是否可以由第一个 i 硬币类型组成。您还没有计算所有的解决方案,您只需找出每个currenti 是否存在至少一个解决方案。然后,您编写一个类似于您已经编写的递归函数,但在尝试每种组合之前,您使用 DP 表检查是否值得尝试,即是否至少存在一个解决方案。比如:

void helper(vector<int>& curCoins, int current, int i){
    if (!solutionExists[current, i]) return; 
    // then your code goes

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

还请注意,只有当您确实需要输出所有组合时,所有这些都是值得的。如果您需要对获得的组合做其他事情,很可能您实际上并不需要所有组合,您可以为此调整 DP 解决方案。例如,如果您只想打印所有解决方案中的m-th,则可以在O(N*S) 中完成。

【讨论】:

    【解决方案4】:

    您只需要对数据结构进行两次遍历(只要您的硬币数量相对较少,哈希表就可以很好地工作)。

    第一个发现所有唯一总和小于所需总和(实际上您可能会在所需总和的 1/2 处停止)并记录获得该总和的最简单方法(所需的加法最少)。这与 DP 基本相同。

    然后第二遍从所需的总数开始,并向后遍历数据以输出可以生成总数的所有方式。

    这最终成为 Petr 建议的两阶段方法。

    【讨论】:

      【解决方案5】:

      使用纯递归穷举技术(代码如下),数量 {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();
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2016-01-25
        • 1970-01-01
        • 2012-08-05
        • 2013-12-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-09-02
        • 1970-01-01
        相关资源
        最近更新 更多