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

例题

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

相关推荐
C++忠实粉丝几秒前
计算机网络socket编程(3)_UDP网络编程实现简单聊天室
linux·网络·c++·网络协议·计算机网络·udp
我们的五年26 分钟前
【Linux课程学习】:进程描述---PCB(Process Control Block)
linux·运维·c++
程序猿阿伟42 分钟前
《C++ 实现区块链:区块时间戳的存储与验证机制解析》
开发语言·c++·区块链
爱摸鱼的孔乙己1 小时前
【数据结构】链表(leetcode)
c语言·数据结构·c++·链表·csdn
烦躁的大鼻嘎2 小时前
模拟算法实例讲解:从理论到实践的编程之旅
数据结构·c++·算法·leetcode
IU宝2 小时前
C/C++内存管理
java·c语言·c++
fhvyxyci2 小时前
【C++之STL】摸清 string 的模拟实现(下)
开发语言·c++·string
C++忠实粉丝2 小时前
计算机网络socket编程(4)_TCP socket API 详解
网络·数据结构·c++·网络协议·tcp/ip·计算机网络·算法
古月居GYH2 小时前
在C++上实现反射用法
java·开发语言·c++
Betty’s Sweet2 小时前
[C++]:IO流
c++·文件·fstream·sstream·iostream