使用动态编程进行更改的所有解决方案

all solutions to change making with dynamic programming

本文关键字:解决方案 动态 编程      更新时间:2023-10-16

我在复习算法课的讲义时,开始思考这个问题:

给定具有不同价值的不同类型的硬币,找到所有硬币配置,将其相加为一定的总和,而不会重复

在课堂上,我们解决了这个问题,找出求和的所有可能方法的数量,以及求和的最少硬币数量。然而,我们从未试图真正找到解决方案。

我在考虑用动态编程来解决这个问题。

我带来了递归版本(为了简单起见,我只打印解决方案):

void solve(vector<string>& result, string& currSoln, int index, int target, vector<int>& coins)
{
    if(target < 0)
    {
        return;
    }
    if(target == 0)
    {
        result.push_back(currSoln);
    }
    for(int i = index; i < coins.size(); ++i)
    {
        stringstream ss;
        ss << coins[i];
        string newCurrSoln = currSoln + ss.str() + " ";
        solve(result, newCurrSoln, i, target - coins[i], coins);
    }
}

然而,我在尝试使用DP来解决问题时遇到了困难。我有两个主要障碍:

  1. 我不知道应该使用什么数据结构来存储以前的答案
  2. 我不知道我的自下而上的过程(使用循环来替换递归)应该是什么样子

欢迎任何帮助,并将感谢一些代码!

谢谢你抽出时间。

在dp解决方案中,您可以生成一组中间状态,以及有多少种方法可以实现这些状态。那么你的答案是最终处于成功状态的数字。

所以,对于变化计数,状态是指你得到了特定的变化量。计数是做出改变的方式的数量。成功的状态是你做出了正确的改变。

要从计数解决方案到枚举它们,您需要保留这些中间状态,并在每个状态中记录所有转换到该状态的状态,以及有关如何转换的信息。(在零钱计数的情况下,如何添加哪枚硬币。)

现在,有了这些信息,您可以从成功状态开始,递归地通过dp数据结构向后,以实际找到解决方案,而不是计数。好消息是,你所有的递归工作都是有效的——你总是只寻找成功的路径,所以不要在不起作用的事情上浪费时间。但是,如果有10亿个解决方案,那么就没有什么捷径可以快速打印出10亿个方案。

不过,如果你想稍微聪明一点,你可以把它变成一个可用的枚举。例如,你可以说"我知道有4323431个解决方案,第432134个是什么?"找到这个解决方案会很快。

很明显,您可以采用动态编程方法。不明显的是,在大多数情况下(取决于硬币的面额),你可以使用贪婪算法,这可能更有效。参见Cormen,Leiserson,Rivest,Stein:算法导论第二版,问题16.1。