LeetCode刷题笔记 动态规划 背包问题
背包问题:有 N 个物品和容量为 W 的背包,每个物品都有自己的体积 w 和价值 v,求拿哪些物品可以使得背包所装下物品的总价值最大。如果限定每种物品只能选择 0 个或 1 个,则问题称为 0-1 背包问题;如果不限定每种物品的数量,则问题称为完全背包问题。
0-1 背包问题,我们可以定义一个二维数组 dp
存储最大价值,其中 dp[i][j]
表示前 i 件物品体积不超过 j 的情况下能达到的最大价值。在我们遍历到第 i 件物品时,在当前背包总容量为 j 的情况下,分为两种情况:(1)如果我们不将物品 i 放入背包,即当前背包容量不足或这放入当前物品无法达到最大价值,那么 dp[i][j]= dp[i-1][j]
,即前 i 个物品的最大价值等于只取前 i -1 个物品时的最大价值;(2)如果我们将物品 i 放入背包,假设第 i 件物品体积为 w,价值为 v,那么我们得到 dp[i][j] = dp[i-1][j-w] + v
。
int knapsack(vector<int> weights, vector<int> values, int N, int W) {vector<vector<int>> dp(N+1,vector<int>(W+1));// 放置第i个物品for(int i=1;i<=N;++i){// 第 i 个物品的体积w和价值vint w = weights[i-1], v = values[i-1];// 遍历容量for(int j=1;j<=W;++j){if(j>=w){dp[i][j] = max(dp[i-1][j],dp[i-1][j-w]+v);}else{dp[i][j] = dp[i-1][j];}}}return dp[N][W];
}
在完全背包问题中,一个物品可以拿多次。这里直接给出完全背包问题的状态转移方程dp[i][j] = max(dp[i-1][j], dp[i][j-w] + v)
,其与 0-1 背包问题的差别仅仅是把状态转移方程中的第二个 i-1 变成了 i。
int knapsack(vector<int> weights, vector<int> values, int N, int W) {vector<vector<int>> dp(N+1,vector<int>(W+1));// 放置第i个物品for(int i=1;i<=N;++i){// 第 i 个物品的体积w和价值vint w = weights[i-1], v = values[i-1];// 遍历容量for(int j=1;j<=W;++j){if(j>=w){dp[i][j] = max(dp[i-1][j],dp[i][j-w]+v);}else{dp[i][j] = dp[i-1][j];}}}return dp[N][W];
}
416 分割等和子集
给定一个正整数数组,求是否可以把这个数组分成和相等的两部分。
输入是一个一维正整数数组,输出时一个布尔值,表示是否可以满足题目要求。
输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。
解析:
本题等价于 0-1 背包问题,设所有数字和为 sum,我们的目标是选取一部分物品,使得它们的总和为 target = sum/2,及用部分物品填满容量为target的背包。同时本题不需要考虑价值,因此我们只需要通过一个布尔值矩阵来表示状态转移矩阵。
设置状态: dp[i][j]
表示 nums 的前 i 个整数是否能够组合成和为 j
状态转移方程:(01背包问题的子问题都很简单只用区分选择当前物品或者不选择当前物品的情况)
不选择
nums[i]
:nums[i]
大于于当前容量 j 时,容量不够不选择该物品,dp[i][j] = dp[i-1][j]
选择
nums[i]
:nums[i]
恰好等于当前容量 j,即nums[i]==j
时,只放该物品就可以填满容量为 j 的背包,dp[i][j] =true
nums[i]
小于当前容量 j,即nums[i]<j
时,只放该物品不够填满容量为 j 的背包,检测放入该物品后dp[i-1][j-nums[i]]
的情况下是否可以填满背包
总的说,当容量足够时,物品nums[i] 可选可不选(在有价值的情况下就需要选择价值较大的情况),则
dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i]]
初始情况:容量为0的时候,不选择任何物品,dp[i][0] = true
;只有一个物品的时候,当容量为j == nums[0]
时恰好满足,即dp[0][nums[0]] = true
示例的状态转移矩阵根据上述状态转移方程可以得到如下表格:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
[0] = 1 | T | T | F | F | F | F | F | F | F | F | F | F |
[1] = 5 | T | T | F | F | F | T | T | F | F | F | F | F |
[2] = 11 | T | T | F | F | F | T | T | F | F | F | F | T |
[3] = 5 | T | T | F | F | F | T | T | F | F | F | T | T |
class Solution {public:bool canPartition(vector<int>& nums) {int sum = accumulate(nums.begin(),nums.end(),0);auto maxPos = max_element(nums.begin(),nums.end());int target = sum/2, len = nums.size();// 注意特殊情况,和为奇数或者元素个数小与2都无法进行有效分割,而元素值大于target直接导致越界if(len<2 || sum&1 || *maxPos > target){return false;}vector<vector<bool>> dp(len,vector<bool>(target+1,false));// 初始情况容量为0for(int i=0;i<len;++i){dp[i][0] = true;}// 初始情况物品只有一个dp[0][nums[0]] = true;for(int i=1;i<len;++i){for(int j=1;j<=target;++j){// 背包容量大于当前扫描物品,可选可不选if(j >= nums[i]){dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i]];// 背包容量小于当前扫描物品,没法选}else{dp[i][j] = dp[i-1][j];}}}return dp[len-1][target];}
};
494 目标和
输入一个一维数组和整数,输出一个整数表示构造表达式结果为target的方法数
输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:一共有 5 种方法让最终目标和为 3 。
-1 + 1 + 1 + 1 + 1 = 3
+1 - 1 + 1 + 1 + 1 = 3
+1 + 1 - 1 + 1 + 1 = 3
+1 + 1 + 1 - 1 + 1 = 3
+1 + 1 + 1 + 1 - 1 = 3
解析:
本题可以转化为 01 背包问题,假设数组元素总和为 sum,添加 '+'
的元素和为 x,那么添加 '-'
的元素和为 sum - x
。由此可以得到 x - (sum - x) = target
,即为x = (target + sum) / 2
,那么本题就可以转化为使用 nums
中的N个物品装满容量为 x 的背包,共有多少种方法,可以看出这时一个组合问题。
设置状态:使用一个二维数组 dp[i][j]
表示数组 nums 中从开头到以 i 位置结尾的所有物品装满容量为 j 的背包的方法数。
状态转移方程:考虑第 i 个元素,如果当前背包容量 j<nums[i]
,则不能放入第 i 个元素 dp[i][j] = dp[i-1][j]
;如果当前背包容量j >= nums[i]
,不放入第 i 个元素的方法数为dp[i][j] = dp[i-1][j]
,放入第 i 个元素的方法数为dp[i][j] = dp[i-1][j-nums[i]]
,那么总的方法数为这两中情况之和即dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i]]
边界情况:如果(target + sum)
不为偶数是无解情况,如果 abs(target) > sum
也是无解情况,没有任何构造方式能够满足题目条件。当数组为空,背包容量为0时dp[0][0] == 1
class Solution {public:int findTargetSumWays(vector<int>& nums, int target) {int len = nums.size();int sum = accumulate(nums.begin(),nums.end(),0);if((target+sum)&1 || abs(target)>sum){return 0;}int weight = (target + sum)/2;vector<vector<int>> dp(len+1,vector<int>(weight+1));dp[0][0] = 1;for(int i=1;i<=len;++i){for(int j=0;j<=weight;++j){dp[i][j] = dp[i-1][j];if(j>=nums[i-1]){dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i-1]];}}}return dp[len][weight];}
};
474 一和零
给定 m 个数字 0 和 n 个数字 1,以及一些由 0-1 构成的字符串,求利用这些数字最多可以构成多少个给定的字符串,字符串只可以构成一次。
输入两个整数 m 和 n,表示 0 和 1 的数量,以及一个一维字符串数组,表示待构成的字符串;输出是一个整数,表示最多可以生成的字符串个数。
输入:strs = [“10”, “0001”, “111001”, “1”, “0”], m = 5, n = 3
输出:4
解释:最多有 5 个 0 和 3 个 1 的最大子集是 {“10”,“0001”,“1”,“0”} ,因此答案是 4 。其他满足题意但较小的子集包括 {“0001”,“1”} 和 {“10”,“1”,“0”} 。{“111001”} 不满足题意,因为它含 4 个 1 ,大于 n 的值 3 。
解析:
本题也是一个01背包问题,而其特点在于它有两个背包,一个装0,另一个装1。
设置状态:dp[i][j][k]
表示装0背包容量为 j,装1背包容量为k的情况下,能够装入前 i 个物品中的几个
状态转移方程:仍旧划为装入当前物品和不装入当前物品两个子问题,不装入则dp[i][j][k]=dp[i-1][j][k]
,装入则dp[i][j][k]=max(dp[i-1][j][k],dp[i-1][j-w0[i]][k-w1[i]] + 1)
,其中w0表示物品中0的体积,w1表示物品中1的体积。
class Solution {public:// 计算每个str中0和1的数量pair<int,int> countWeight(string str){int count0 = 0, count1 = 0;for(int i=0;i<str.length();++i){if(str[i] == '0'){++count0;}else{++count1;}}return make_pair(count0,count1);}int findMaxForm(vector<string>& strs, int m, int n) {int len = strs.size();// 用一个三维数组表示在前 i 个字符串中,使用 j 个 0 和 k 个 1 的情况下最多可以得到的字符串数量vector<vector<vector<int>>> dp(len+1,vector<vector<int>>(m+1,vector<int>(n+1)));for(int i=1;i<=len;++i){auto count = countWeight(strs[i-1]);// j 和 k 都从零开始,因为str存在'0'或'1'这种只有一种元素构成的情况for(int j=0;j<=m;++j){for(int k=0;k<=n;++k){if(j >= count.first && k >= count.second){dp[i][j][k] = max(dp[i-1][j][k],dp[i-1][j-count.first][k-count.second] + 1);}else{dp[i][j][k] = dp[i-1][j][k];}}}}return dp[len][m][n];}
};
322 零钱兑换
给定一些硬币的面额,求最少可以用多少颗硬币组成给定的金额。
输入一个一维整数数组,表示硬币的面额;以及一个整数,表示给定的金额。输出一个整数,表示满足条件的最少的硬币数量。若不存在解,则返回-1。
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1
解析:
因为每个硬币可以用无限多次,这道题本质上是完全背包问题。完全背包问题还是没搞懂,记录一下,以后仔细琢磨。
class Solution {public:int coinChange(vector<int>& coins, int amount) {int Max = amount + 1;vector<int> dp(amount + 1, Max);dp[0] = 0;for (int i = 1; i <= amount; ++i) {for (int j = 0; j < (int)coins.size(); ++j) {if (coins[j] <= i) {dp[i] = min(dp[i], dp[i - coins[j]] + 1);}}}return dp[amount] > amount ? -1 : dp[amount];}
};
参考资料
LeetCode 101:和你一起轻松刷题(C++) 第 7 章 深入浅出动态规划
LeetCode刷题笔记 动态规划 背包问题相关推荐
- LeetCode刷题笔记-动态规划-day4
文章目录 LeetCode刷题笔记-动态规划-day4 55. 跳跃游戏 1.题目 2.解题思路 3.代码 45. 跳跃游戏 II 1.题目 2.解题思路 3.代码 LeetCode刷题笔记-动态规划 ...
- LeetCode刷题笔记 动态规划 股票交易问题
股票交易类问题通常可以用动态规划来解决.对于稍微复杂一些的股票交易类问题,比如需要冷却时间或者交易费用,则可以用通过动态规划实现的状态机来解决. 题目 特点 121 买卖股票的最佳时机 只能买卖一 ...
- 【leetcode刷题笔记】动态规划
#[leetcode刷题笔记]动态规划 石子游戏 public boolean stoneGame(int[] piles) {int N = piles.length;// dp[i][j] is ...
- LeetCode刷题笔记(算法思想 四)
LeetCode刷题笔记(算法思想 四) 七.动态规划 斐波那契数列 70. 爬楼梯 198. 打家劫舍 213. 打家劫舍 II 信件错排 母牛生产 矩阵路径 64. 最小路径和 62. 不同路径 ...
- 卷进大厂系列之LeetCode刷题笔记:二分查找(简单)
LeetCode刷题笔记:二分查找(简单) 学算法,刷力扣,加油卷,进大厂! 题目描述 涉及算法 题目解答 学算法,刷力扣,加油卷,进大厂! 题目描述 力扣题目链接 给定一个 n 个元素有序的(升序) ...
- LeetCode刷题笔记2——数组2
LeetCode刷题笔记2--数组2 重塑数组 题目 在 MATLAB 中,有一个非常有用的函数 reshape ,它可以将一个 m x n 矩阵重塑为另一个大小不同(r x c)的新矩阵,但保留其原 ...
- 小何同学的leetcode刷题笔记 基础篇(01)整数反转
小何同学的leetcode刷题笔记 基础篇(01)整数反转[07] *** [01]数学取余法*** 对数字进行数位操作时,常见的方法便是用取余的方法提取出各位数字,再进行操作 操作(1):对10取余 ...
- LeetCode刷题笔记汇总
LeetCode刷题笔记汇总 第一次刷LeetCode写的一些笔记. 1.两数之和 3.无重复字符的最长子串 15.三数之和 18.四数之和 19.删除链表的倒数第 N 个结点 20.有效的括号 21 ...
- LeetCode刷题笔记- 15.三数之和
LeetCode刷题笔记- 15.三数之和 C语言 题目 注意点 C语言 /*** Return an array of arrays of size *returnSize.* The sizes ...
最新文章
- 设置grep高亮显示匹配项
- iis7 文件服务器搭建,iis7 ftp服务器搭建
- MindSpore!这款刚刚开源的深度学习框架我爱了!
- 他是奥运会银牌获得者,还是世界知名数学家,他的家族让世界为之倾倒
- Centos7 之目录处理命令(八)
- 创建LEANGOO账号
- 好书一本:《设计心理学》
- Windows Mobile 6.1开发笔记
- SQL Server 高可用性(六)日志传送
- gdb调试出现“no debugging symbols found”
- html5播放器 php,PHP/HTML5页面上的随机声音播放器
- v-if绑定的元素为什么事件没有响应
- 上手阿里云服务器(一)——搭建LAMP开发环境、防火墙、文件传输
- Android 实时滤镜 高斯模糊(带源码)
- Gradle简单配置
- 拥抱云原生,Java与Python基于gRPC通信
- Qt_Opencv安装
- ie-css3.htc兼容ie8
- CPU性能测试脸型软件,UserBenchmark(电脑性能测试软件) V2.9.1.0 官方版
- 「雅礼集训」市场--势能线段树