赞
踩
动态规划中每一个状态一定是由前面的状态推导出来的
dp数组存储的是可维持的状态,而递推公式是达到该状态的动作
问题 :有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。
所以公式为:dp[i][j] = max{ dp[i-1][j], dp[i - 1][j - weight[i]] + value[i]}
一般可以把数组全部初始化为0, 方便
滚动数组写法(一维)
1.dp[j]表示容量为j的背包,可存放的物品的最大价值
2. 递推公式:
dp[j] = max{ dp[j], dp[j - weight[i]] + value[i]}
3.初始化:注意dp[j]代表的是容量,需要计算一下最大容量的上线
4.遍历顺序!!先物品再容量,内层循环倒序是为了用上一个状态的数值,正序的话会状态会被更新
for(int i = 0; i < weight.size(); i++) { // 遍历物品
for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量,且倒序!!
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}
与01背包一维数组不同之处:
而完全背包的物品是可以添加多次的,所以要从小到大去遍历,
且内外层遍历顺序皆可,即:
// 先遍历物品,再遍历背包 for(int i = 0; i < weight.size(); i++) { // 遍历物品 for(int j = weight[i]; j <= bagWeight ; j++) { // 遍历背包容量 dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); } } // 先遍历背包,再遍历物品 for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量 for(int i = 0; i < weight.size(); i++) { // 遍历物品 if (j - weight[i] >= 0) dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); } cout << endl; }
for(int i = 0; i < weight.size(); i++){//遍历物品
for(int j = bagWeight; j >= weight[i]; j--){//遍历容量
for(int k = 1; k < = nums[i]; k++){
dp[j] = max(dp[j], dp[j - weight[i]*k] + value[i]*k);
}
}
}
1、比如面试题,要求先用二维dp数组实现,然后再用一维dp数组实现(),最后在问,两个for循环的先后是否可以颠倒?为什么?
2、求组合数的背包,递推公式怎么推?
不考虑nums[i]的情况下,填满容量为j - nums[i]的背包,有dp[j - nums[i]]种方法。
那么只要搞到nums[i]的话,凑成dp[j]就有dp[j - nums[i]] 种方法。
例如:dp[j],j 为5,
已经有一个1(nums[i]) 的话,有 dp[4]种方法 凑成 dp[5]。
已经有一个2(nums[i]) 的话,有 dp[3]种方法 凑成 dp[5]。
已经有一个3(nums[i]) 的话,有 dp[2]中方法 凑成 dp[5]
已经有一个4(nums[i]) 的话,有 dp[1]中方法 凑成 dp[5]
已经有一个5 (nums[i])的话,有 dp[0]中方法 凑成 dp[5]
那么凑整dp[5]有多少方法呢,也就是把 所有的 dp[j - nums[i]] 累加起来。
所以求组合类问题的公式,都是类似这种:
dp[j] += dp[j - nums[i]]
3、
如果求组合数就是外层for循环遍历物品,内层for遍历背包。(即保证物品的放入顺序) 518题
理由:来自官方解答
如果求排列数就是外层for遍历背包,内层for循环遍历物品。 377题
理由:来自官方解答
4、
leetcode上没有纯01/完全背包的问题,都是应用方面的题目,也就是需要转化为背包问题,转换的思路是最关键的
5、背包套路总结
https://github.com/youngyangyang04/leetcode-master/blob/master/problems/%E8%83%8C%E5%8C%85%E6%80%BB%E7%BB%93%E7%AF%87.md
小偷也要有文化,看题
本题其实和416. 分割等和子集几乎是一样的,只是最后对dp[target]的处理方式不同。
416. 分割等和子集相当于是求背包是否正好装满,而本题是求背包最多能装多少。
在求装满背包有几种方法的情况下,递推公式一般为:
dp[j] += dp[j - nums[i]] (相当于一个矩阵的同一列加起来)
上题的排列版本,题目链接
注意两个关键问题:
把房间的格局变成为二叉树结构题目链接
方法1:递归
一个节点的金额数有两种情况,case 1:爷爷节点偷+四个孙子偷;case 2: 以及只有两个孩子偷; 所以需要后序遍历
直接搜索超时,因此用记忆化搜索避免一些重复子问题(一个节点计算时会算其孩子和孙子节点的数值)
方法2: 动态规划,前面说到有重复计算,用到之前状态。在上面方法,其实对一个节点 偷与不偷得到的最大金钱都没有做记录,而是需要实时计算。而动态规划其实就是使用状态转移容器来记录状态的变化,避免实时计算,
推荐参考链接:https://leetcode.cn/problems/house-robber-iii/solution/san-chong-fang-fa-jie-jue-shu-xing-dong-tai-gui-hu/
两种状态:dp[i][0]第i天持有股票所得最多现金; dp[i][1]表示第i天不持有股票最多现金
如果第i天持有股票即dp[i][0], 那么可以由两个状态推出来
第i-1天就持有股票,那么就保持现状,所得现金就是昨天持有股票的所得现金 即:dp[i - 1][0]
第i天买入股票,所得现金就是买入今天的股票后所得现金即:-prices[i](这里加粗是因为二刷的时候发现容易写成dp[i-1][1] - prices[i] ,但这种写法是下面122多次买卖的写法,如果最多一次买卖,转移方程直接用-prices[i] )
那么dp[i][0]应该选所得现金最大的,所以dp[i][0] = max(dp[i - 1][0], -prices[i]);
如果第i天不持有股票即dp[i][1], 也可以由两个状态推出来
第i-1天就不持有股票,那么就保持现状,所得现金就是昨天不持有股票的所得现金 即:dp[i - 1][1]。
第i天卖出股票,所得现金就是按照今天股票佳价格卖出后所得现金即:prices[i] + dp[i - 1][0]
同样dp[i][1]取最大的,dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);
初始化,dp[0][0] = -pricces[0]; dp[0][1] = 0;
多次买卖一支股票,但卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。题目链接
老套路,找状态
一天的交易状态为: [持股, 不持股且过了冷冻期, 不持股且在冷冻期]
dp[i][k]为第i天相应状态的最大金额
嘻嘻,自己写出来了
if(nums1[i-1] == nums2[j-1])
dp[j] = dp[j - 1] + 1;
else
dp[j] = 0; //注意要赋0操作,因为不相等的时候为0,重新计算
if(text1[i - 1] == text2[j - 1])
dp[i][j] = dp[i - 1][j - 1] + 1;
else
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1] );
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]; if(s[i] == s[j])(左上方和上方)
dp[i][j] = dp[i - 1][j]; otherwise
dp[i][j] = min({dp[i - 1][j - 1] + 2, dp[i][j - 1] + 1, dp[i - 1][j] + 1});
// dp[i][j] = min(min(dp[i - 1][j - 1] + 2, dp[i][j - 1] + 1), dp[i - 1][j] + 1);
for(int i = 0; i < s.size() ; i++){
count += extend(s, i, i);
count += extend(s, i, i+1);
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。