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

例题

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

相关推荐
小飞猪Jay2 小时前
C++面试速通宝典——13
jvm·c++·面试
rjszcb3 小时前
一文说完c++全部基础知识,IO流(二)
c++
小字节,大梦想3 小时前
【C++】二叉搜索树
数据结构·c++
吾名招财3 小时前
yolov5-7.0模型DNN加载函数及参数详解(重要)
c++·人工智能·yolo·dnn
我是哈哈hh4 小时前
专题十_穷举vs暴搜vs深搜vs回溯vs剪枝_二叉树的深度优先搜索_算法专题详细总结
服务器·数据结构·c++·算法·机器学习·深度优先·剪枝
憧憬成为原神糕手4 小时前
c++_ 多态
开发语言·c++
郭二哈4 小时前
C++——模板进阶、继承
java·服务器·c++
挥剑决浮云 -4 小时前
Linux 之 安装软件、GCC编译器、Linux 操作系统基础
linux·服务器·c语言·c++·经验分享·笔记
丶Darling.4 小时前
LeetCode Hot100 | Day1 | 二叉树:二叉树的直径
数据结构·c++·学习·算法·leetcode·二叉树
labuladuo5205 小时前
Codeforces Round 977 (Div. 2) C2 Adjust The Presentation (Hard Version)(思维,set)
数据结构·c++·算法