文章目录
- 前言
- 打家劫舍
- 1.lc198 打家劫舍
- 2.lc213 打家劫舍II
- 3.lc337 打家劫舍 III
- 买卖股票
- 0.模板套路
- 1.lc121 买卖股票的最佳时机
- 2.lc122 买卖股票的最佳时机II
- 3.lc714 买卖股票的最佳时机含手续费
- 4.lc309. 最佳买卖股票时机含冷冻期
- 5. lc123 买卖股票的最佳时机 III
- 6. lc188 买卖股票的最佳时机 IV
前言
对于动态规划来说,有两个比较有意思的专题,就是打家劫舍和买卖股票,也可以说是经典问题
打家劫舍
1.lc198 打家劫舍
lc198链接
描述:
如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例:
输入:[2,7,9,3,1]
输出:12
Solution:
dp[i]:考虑下标i(包括i)以内的房屋,最多可以偷窃的金额为dp[i]。
递推公式的精髓在于,站在某一个房子处考虑你是偷还是偷,如果偷,那前一个房子就不能被考虑;如果偷,那金额的最大就来自于偷前一个房屋积累的。
public int rob(int[] nums) {int[] dp = new int[nums.length];if(nums.length==1) return nums[0];dp[0] = nums[0];dp[1] = Math.max(nums[0],nums[1]);for(int i=2;i<nums.length;i++){dp[i] = Math.max(dp[i-2]+nums[i],dp[i-1]); }return dp[nums.length-1];}
2.lc213 打家劫舍II
lc213 链接
描述:
在上一题基础上增加约束 房屋围成一圈
示例:
输入:nums = [2,3,2] 输出:3(不能2+2,因为是相邻的)
Solution:
把这个序列拆成两段,第一段不包括尾元素,第二段不包括首元素
public int rob(int[] nums) {if(nums.length==1){return nums[0];}int [] dp1 = new int[nums.length];int [] dp2 = new int[nums.length];dp1[0] = nums[0];dp1[1] = nums[0];dp2[1] = nums[1];for(int i =2; i<nums.length; i++){dp1[i] = Math.max(dp1[i-2]+nums[i],dp1[i-1]);dp2[i] = Math.max(dp2[i-2]+nums[i],dp2[i-1]);}return Math.max(dp1[nums.length-2],dp2[nums.length-1]);}
3.lc337 打家劫舍 III
lc337链接
描述:二叉树偷窃
示例:
输入: root = [3,2,3,null,3,null,1] 输出: 7
Solution:
买卖股票
0.模板套路
买卖股票一共有6题,可以用一个统一的框架去做(https://labuladong.github.io)
整个买卖股票涉及的【状态】最多有三个(所以根据题意要不要用三维dp)
- 第一个是天数
- 第二个是允许交易的最大次数
- 第三个是当前的持有状态(可以设 1 表示持有,0 表示没有持有)
dp[i][k][0 or 1] 0 <= i <= n - 1, 1 <= k <= K
n 为天数,大 K 为交易数的上限,0 和 1 代表是否持有股票。
此问题共 n × K × 2 种状态,全部穷举就能搞定。
遍历:
for 0 <= i < n:for 1 <= k <= K:for s in {0, 1}:dp[i][k][s] = max(buy, sell, rest)
举个例子:比如说 dp[3][2][1] 的含义就是:今天是第三天,我现在手上持有着股票,至今最多进行 2 次交易
最后要求的答案是:dp[n - 1][K][0](即第n天,交易K次,卖出状态下的利润)
然后状态转移方程分为两步,分别是此时不持有股票的最大利润怎么推导,和此时持有股票的最大利润怎么推导来。
// 今天没有持有= 昨天没有持有+昨天持有(今天卖了,所以利润增加)
dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
// 今天持有= 昨天持有+ 昨天没持有(今天买了,利润减少,同时交易次数减1)
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])// base case 情况
dp[0][……][0] = 0 (第一天没持有,利润为0)
dp[0][……][1] = -prices[0] (一开始就持有,所以第一天就买入,所以利润为负)
dp[……][0][0] = 0 (交易次数是0次,即不能交易,所以利润为0)
//dp[...][0][1] = -infinity
1.lc121 买卖股票的最佳时机
lc121链接
描述:
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
示例:
输入:[7,1,5,3,6,4] 输出:5
Solution:
利用上面的统一框架,这里没有限制交易次数,所以用一个二维dp就可以了
public int maxProfit (int[] prices) {// write code hereint[][] dp = new int[prices.length][2];// dp初始化dp[0][1] = -prices[0];dp[0][0] = 0;for(int i=1;i<prices.length;i++){// 0表示未持有dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]+prices[i]);// 1表示持有(注意这里如果前一天未持有则利润肯定为0)dp[i][1] = Math.max(-prices[i],dp[i-1][1]);}return dp[prices.length-1][0];}}
2.lc122 买卖股票的最佳时机II
lc22链接
相比较上题,不限制交易次数(即k为正无穷)
示例:
输入:prices = [7,1,5,3,6,4]
输出:7(5-1+6-3)
Solution:
按照统一模板,有k这个状态,但是此时的k又是无穷状态(所以k与k-1可以看成一样的,也就是可以不用三维dp)
与上一题相比只需要修改状态转移方程
// 0表示未持有
dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]+prices[i]);
// 1表示持有(这里相比于上题的区别是第i天持有,第i-1天未持有但是可以有利润)
dp[i][1] = Math.max(dp[i-1][0]-prices[i],dp[i-1][1]);
3.lc714 买卖股票的最佳时机含手续费
lc714链接
描述:
你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了
示例:
输入:prices = [1, 3, 2, 8, 4, 9], fee = 2
输出:8
Solution:
与上题相似的思路,k依然是无穷,只不过多了个手续费。只需稍微修改状态转移和初始化状态:
dp[0][1] = -prices[0]-fee;dp[0][0] = 0;for(int i=1;i<prices.length;i++){// 0表示未持有dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]+prices[i]);// 1表示持有dp[i][1] = Math.max(dp[i-1][0]-prices[i]-fee,dp[i-1][1]);}
4.lc309. 最佳买卖股票时机含冷冻期
lc309链接
描述:
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
示例:
输入: prices = [1,2,3,0,2]
输出: 3
Solution:
与前两题相比,k依然是无穷,但多了个冷冻期,冷冻期为1天。 所以第 i 天选择 buy 的时候,要从 i-2 的状态转移,而不是 i-1 。
public int maxProfit(int[] prices) {if(prices.length==1) return 0;int[][] dp = new int[prices.length][2];// dp初始化dp[0][1] = -prices[0];dp[0][0] = 0;dp[1][0] = Math.max(dp[0][0],dp[0][1]+prices[1]);dp[1][1] = Math.max(-prices[1],dp[0][1]);for(int i=2;i<prices.length;i++){// 0表示未持有dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]+prices[i]);// 1表示持有dp[i][1] = Math.max(dp[i-2][0]-prices[i],dp[i-1][1]);}return dp[prices.length-1][0];
}
5. lc123 买卖股票的最佳时机 III
lc123链接
描述:
你最多可以完成 两笔 交易
示例:
输入:prices = [3,3,5,0,0,3,1,4]
输出:6
Solution1:
k=2的情况,引入3维dp。因为k比较小,可以枚举状态
public int maxProfit(int[] prices) {int len = prices.length;// dp[i][k][s]int[][][] dp = new int[len][3][2];dp[0][1][0] = 0;dp[0][1][1] = -prices[0];dp[0][2][0] = 0;dp[0][2][1] = -prices[0];for(int i=1;i<len;i++){// 今天没有持有= 昨天没有持有+昨天持有(今天卖了,所以利润增加)// dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])// 今天持有= 昨天持有+ 昨天没持有(今天买了,利润减少,同时交易次数减1)// dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])dp[i][1][0] = Math.max(dp[i-1][1][0],dp[i-1][1][1]+prices[i]);dp[i][1][1] = Math.max(dp[i-1][1][1],-prices[i]);dp[i][2][0] = Math.max(dp[i-1][2][0],dp[i-1][2][1]+prices[i]);dp[i][2][1] = Math.max(dp[i-1][2][1],dp[i-1][1][0]-prices[i]);}return dp[len-1][2][0];}
Solution2:
不仅穷举天数状态,还要枚举交易次数状态。所以两个for循环
在这里插入代码片
6. lc188 买卖股票的最佳时机 IV
lc188链接
至多k笔交易
输入:k = 2, prices = [2,4,1]
输出:2
Solution:
大boss,三种状态都要穷举,参考上面的解法2