[英]Is Coin Change Algorithm That Output All Combinations Still Solvable By DP?
例如,總金額應為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版本: 硬幣更改DP算法打印所有組合
動態搜索不太可能使窮舉搜索“更好”,但是這是一個可行的解決方案:
從2d組合字符串數組開始,arr [value] [index]其中value是硬幣的總價值。 設X為目標值;
從arr [0] [0] =“”開始; 對於每個硬幣面額n,從i = 0到Xn,請將所有字符串從arr [i]復制到arr [i + n],並將n附加到每個字符串。
例如,如果n = 5,您將得到arr [0] [0] =“”,arr [5] [0] =“ 5”和arr [10] [0] =“ 5 5”
希望有道理。 典型的DP只會計數而不是使用字符串(您也可以使用int vector替換字符串以保持計數)
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];
}
現場演示 。
假設您的期望輸出的總大小為K
(所有組合中的硬幣總數)。 顯然,如果您實際上需要輸出所有解決方案,那么您將無法提供比O(K)
更快運行的解決方案。 由於K
可能很大,因此運行時間將非常長,並且在最壞的情況下,動態編程不會給您帶來什么好處。
但是,您仍然可以比直接的遞歸解決方案做得更好。 即,您可以在O(N*S+K)
運行一個解決方案,其中N
是您擁有的硬幣數量, S
是總數。 對於最壞的K
,這並不比直接解決方案更好,但是,如果K
值不那么大,則它將比遞歸解決方案運行得更快。
該O(N*S+K)
解決方案可以相對簡單地進行編碼。 首先運行標准的DP解決方案,找出每個總和current
和每個i
和是否current
可以由第一的i
硬幣類型。 您尚未計算所有解,您只需找出每個current
和i
是否至少存在一個解。 然后,您編寫與您已經編寫的函數類似的遞歸函數,但是在嘗試每種組合之前,請使用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
個,則可以在O(N*S)
。
您只需要對數據結構進行兩次傳遞(只要您擁有相對較少的硬幣,哈希表就可以很好地工作)。
第一個發現所有唯一的總和小於期望的總和(實際上,您可能會停止在期望的總和的1/2處),並記錄最簡單的方法(需要最少的加法)來獲得該總和。 這與DP基本相同。
然后,第二遍以所需的總數開始,並向后遍歷數據,以輸出可以生成總數的所有方式。
這最終是Petr建議的兩階段方法。
使用純遞歸窮舉技術(以下代碼),數量{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();
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.