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

例题

例题: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)

相关推荐
王老师青少年编程26 分钟前
csp信奥赛C++高频考点专项训练之贪心算法 --【排序贪心】:魔法
c++·算法·贪心·csp·信奥赛·排序贪心·魔法
晓觉儿37 分钟前
【GPLT】2026年第十一届团队程序设计天梯赛赛后题解(已写2h,存档中)
数据结构·c++·算法·深度优先·图论
6Hzlia1 小时前
【Hot 100 刷题计划】 LeetCode 394. 字符串解码 | C++ 单栈回压法
c++·算法·leetcode
流年如夢2 小时前
自定义类型进阶:联合与枚举
java·c语言·开发语言·数据结构·数据库·c++·算法
Little At Air2 小时前
C++stack模拟实现
linux·开发语言·c++·算法
rayyy92 小时前
c++, sizeof(string)和string.size()有什么区别
c++
郭涤生3 小时前
C++ 回调较容易出错问题
开发语言·c++
yi.Ist3 小时前
2025CCPC郑州邀请赛
c++·学习·算法·acm
图码3 小时前
递归入门:从n到1的优雅打印之旅
数据结构·c++·算法·青少年编程·java-ee·逻辑回归·python3.11
大肥羊学校懒羊羊3 小时前
题解:计算约数个数
数据结构·c++·算法