簡體   English   中英

DP是否可以解決輸出所有組合的找零算法?

[英]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硬幣類型。 您尚未計算所有解,您只需找出每個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個,則可以在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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM