图解二维完全背包问题——降维打击

例题

例题:518. 零钱兑换 II

概述:

给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。
请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。
假设每一种面额的硬币有无限个。
题目数据保证结果符合 32 位带符号整数。

朴素的二维完全背包

想法:

完全背包问题:即为假设可选择的物品为无限个,在数学本质上是组合问题。

在本例中,需要求取满足sum=amount的不重复组合数量。

显然,最先容易想到的是二维背包方法,即为遍历coins数组,选择当前所有可能的硬币数量。

定义dp[coins.size()][amount],得出状态转移方程。

在这种情况下,事件复杂度为O(coins.size()*amount^2),空间复杂度为O(coins.size()*amount)

注意到dp过程中的数据传递只在[i]和[i+1]之间发生,此处优化了空间复杂度,但时间复杂度仍然不变。

这里我们给出一个示例代码:

class Solution {
public:
    int change(int amount, vector<int>& coins) {
        vector<int> dp(amount+1, 0);
        dp[0] = 1;
        for(int i=0;i<=coins.size();i++) {
            if(i==coins.size()) {
                return dp[amount];
            }

            vector<int> temp(amount+1, 0);
            for(int j=0;j*coins[i]<=amount;j++) {
                int sum = j*coins[i];
                for(int k=amount;k-sum>=0;k--) {
                    temp[k] += dp[k-sum];
                }
            }
            swap(dp, temp);
        }
        return 0;
    }
};

降维

尝试运行以上代码,发现虽然能通过测试,但是耗时高到天际,显然不是一个好的解决方案。

这里进入今天的主题,二维dp降阶。事实上在上文代码中已经完成了空间层面的降阶,只需要考虑时间层面。

我们模拟其中一次转移的代码,进入循环for(int i=0;i<=coins.size();i++) {...}

假设此时amount = 4,coins[i] = 2

dp初始状态为:

此时刚进入循环,vector temp暂时为空(全0):

第1轮,选择coin number = 0,sum=0,temp[k] += dp[k-0]; 即为将dp中内容拷贝到temp中

第2轮,选择coin number = 1,sum=1*2=2,temp[k] += dp[k-2];

第3轮,选择coin number = 2,sum=2*2=4,temp[k] += dp[k-4];

第4轮,coin number = 3,sum=3*2=6, 6>4,退出本轮循环

由以上图可以看出,循环中每一次相加就相当于对整体数组做了一次向上平移,offset=2。

这里我们想要在一个循环中完成上述的所有工作,可以观察到如下公式:

temp[0] = dp[0]

temp[2] = dp[2] + dp[0] = dp[2] + temp[0]

temp[4] = dp[4] + dp[2] + dp[0] = dp[4] + temp[2]

......

那么我们可以考虑下标从小到大的累加,这样,较大的下标相加的时候就自动处理了前面的部分,在算法上这是一种前缀和(prefix)思想。

这样,我们有如下代码:

class Solution {
public:
    int change(int amount, vector<int>& coins) {
        vector<int> dp(amount+1, 0);
        dp[0] = 1;
        for(int i=0;i<coins.size();i++) {
            for(int j=coins[i];j<=amount;j++) {
                dp[j] += dp[j-coins[i]];
            }
        }
        return dp[amount];
    }
};

此时的时间复杂度为O(coins.size()*amount),空间复杂度为O(amount)

posted @ 2024-03-25 09:04  真昼小天使daisuki  阅读(257)  评论(0编辑  收藏  举报