例如,总金额应为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/
答案 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];
}
答案 2 :(得分:0)
假设您有O(K)
您期望的输出总大小(所有组合中的硬币总数)。显然,如果你真的需要输出所有它们,你就不能拥有比K
更快的解决方案。由于O(N*S+K)
可能非常大,这将是一个非常长的运行时间,在最坏的情况下,您将从动态编程中获得很少的利润。
但是,您仍然可以比直接的递归解决方案做得更好。也就是说,您可以在N
中运行解决方案,其中S
是您拥有的硬币数量,K
是总和。对于最差的K
,这并不比直接解决方案好,但如果O(N*S+K)
不是那么大,那么你的运行速度会比递归解决方案快。
此current
解决方案可以相对简单地编码。首先,您运行标准DP解决方案,以找出每个总和i
和每个current
总和i
是否可以由第一个current
硬币类型组成。您尚未计算所有解决方案,只需了解每个i
和void 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();
}
}