第九章 动态规划part08
股票问题是一个动态规划的系列问题,前两题并不难,第三题有难度。
121. 买卖股票的最佳时机
视频讲解:https://www.bilibili.com/video/BV1Xe4y1u77q
https://programmercarl.com/0121.%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BA.html
122.买卖股票的最佳时机II
视频讲解:https://www.bilibili.com/video/BV1D24y1Q7Ls
https://programmercarl.com/0122.%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BAII%EF%BC%88%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%EF%BC%89.html
123.买卖股票的最佳时机III
这道题一下子就难度上来了,关键在于至多买卖两次,这意味着可以买卖一次,可以买卖两次,也可以不买卖。
视频讲解:https://www.bilibili.com/video/BV1WG411K7AR
https://programmercarl.com/0123.%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BAIII.html
121. 买卖股票的最佳时机
题目链接
https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/description/
解题思路
dp[i][0] 第i天持有这支股票的最大现金 (实际就是找买了股票后手里最大现金)
dp[i][1] 第i天不持有这支股票的最大现金 (实际就是找到最大利润)
code
class Solution {
public int maxProfit(int[] prices) {
//1.确定dp数组以及下标的含义
//dp[i][0] 第i天持有这支股票的最大现金 (实际就是找买了股票后手里最大现金)
//dp[i][1] 第i天不持有这支股票的最大现金 (实际就是找到最大利润)
//2.确定递推公式
//dp0依赖前一天推出
//前一天持有或今天持有 去一个手里的最大现金,这里的前一天不是一定是前一天
//有可能是前几天手里的现金已经是最大的了
//dp[i][0]=Math.max(dp[i-1][0],-price[i]);
//可以看到这里是个负数今天买入价格7的股票我的最大现金就是-7
//dp1依赖前一天推出
//前一天(前一天可能是前几天已经推出最大利润)卖出的最大利润 dp[i-1][1]
//和当前天卖出的最大利润dp[i-1][0](前一天(前几天)持有股票的最大现金,这是个)+price[i]
//dp[i][1]=Math.max(dp[i-1][1],dp[i-1][0]+price[i]);
//3.如何初始化数组
int[][] dp=new int[prices.length][2];
//持有
dp[0][0]=-prices[0];
//不持有
dp[0][1]=0;
//4.确定遍历顺序
//从递推公式可以看出dp[i]都是由dp[i - 1]推导出来的,那么一定是从前向后遍历
for(int i=1;i<prices.length;i++){
dp[i][0]=Math.max(dp[i-1][0],-prices[i]);
dp[i][1]=Math.max(dp[i-1][1],dp[i-1][0]+prices[i]);
}
//5.打印dp数组
//最终求的是不持有这支股票的最大现金
return dp[prices.length-1][1];
}
}
122.买卖股票的最佳时机II
题目链接
https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/description/
解题思路
第i天持有这只股票所得最多现金
因为股票I 那道题全程只能买卖一次,所以如果买入股票,那么第i天持有股票即dp[i][0]一定就是 -prices[i]
不同的是这题可以重复买,所以当第i天买入股票的时候,所持有的现金可能有之前买卖过的利润
如果是第i天买入股票,所得现金就是昨天不持有股票的所得现金 减去 今天的股票价格 即:dp[i - 1][1] - prices[i]
dp[i-1][1] 可能表示自己之前买卖过的记录, dp[i-1][1]-prices[i] 表示这次又重新买了
dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]-prices[i]);
第i天不持有这只股票的所得最多现金
dp[i][1]=Math.max(dp[i-1][1],dp[i-1][0]+prices[i]);
code
class Solution {
public int maxProfit(int[] prices) {
int[][] dp=new int[prices.length][2];
dp[0][0]=-prices[0];
dp[0][1]=0;
for(int i=1;i<prices.length;i++){
//第i天持有这只股票的最大金钱 ,
//跟股票I 不同的是 dp[i-1][1]-prices[i],这里是可以重复买股票的
//dp[i-1][1] 可能表示自己之前买卖过的记录, dp[i-1][1]-prices[i] 表示这次又从新买了
dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]-prices[i]);
//第i天不持有这只股票的最大金钱
dp[i][1]=Math.max(dp[i-1][1],dp[i-1][0]+prices[i]);
}
return dp[prices.length-1][1];
}
}
123.买卖股票的最佳时机III
题目链接
https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iii/description/
解题思路
关键在于至多买卖两次,这意味着可以买卖一次,可以买卖两次,也可以不买卖。
接来下我用动态规划五部曲详细分析一下:
1.确定dp数组以及下标的含义
一天一共就有五个状态,
0 没有操作 (其实我们也可以不设置这个状态)
1 第一次持有股票
2 第一次不持有股票
3 第二次持有股票
4 第二次不持有股票
dp[i][j]中 i表示第i天,j为 [0 - 4] 五个状态,dp[i][j]表示第i天状态j所剩最大现金。
2.确定递推公式
dp[i][0]=dp[i-1][0]
其实我们可以不设置,‘0. 没有操作’ 这个状态,因为没有操作,手上的现金自然就是0, 正如我们在 121.买卖股票的最佳时机 和 122.买卖股票的最佳时机II 也没有设置这一状态是一样的。
dp[i][1]=Math.max(dp[i-1][1],-prices[i])
dp[i][2]=Math.max(dp[i-1][2],dp[i-1][1]+prices[i])
dp[i][3]=Math.max(dp[i-1][3],dp[i-1][2]-prices[i])
dp[i][4]=Math.max(dp[i-1][4],dp[i-1][3]+prices[i])
3.如何初始化
第0天没有操作 dp[0][0] =0
第0天第一次持有 dp[0][1]=-prices[i];
第0天第一次不持有 dp[0][2]=0
第二次买入依赖于第一次卖出的状态,其实相当于第0天第一次买入了,第一次卖出了,然后再买入一次(第二次买入),那么现在手头上没有现金,只要买入,现金就做相应的减少。
所以第二次买入操作,初始化为:dp[0][3] = -prices[0];
dp[0][4]=0;
4.确定遍历顺序
dp[i] 依赖dp[i-1] 推出从前向后遍历
5.举例推导dp数组
以输入[1,3,1,7]为例
dp[0][2] 为什么初始化为 -price[i] 第一次可以进行当天买卖 持有就是0 第二次 在进行买就是 -price[0]
把第一次取股票的利润推到第二次,第二次在计算出利润做一个相加
如果不能执行第二次,那么就取第一次的最大结果 如:1,2,3,4,5
代码随想录解释:
第0天第二次买入操作,初始值应该是多少呢?应该不少同学疑惑,第一次还没买入呢,怎么初始化第二次买入呢?
第二次买入依赖于第一次卖出的状态,其实相当于第0天第一次买入了,第一次卖出了,然后再买入一次(第二次买入),那么现在手头上没有现金,只要买入,现金就做相应的减少。
所以第二次买入操作,初始化为:dp[0][3] = -prices[0];
code
class Solution {
public int maxProfit(int[] prices) {
//dp[i][0] 持有第一支股票所得最大金钱
//dp[i][1] 不持有第一支股票所得最大金钱
//dp[i][2] 持有第二支股票所得最大金钱
//dp[i][3] 不持有第二支股票所得最大金钱
//初始化
int[][] dp=new int[prices.length][4];
dp[0][0]=-prices[0];
dp[0][1]=0;
//这里初始化理解就是第0天第一次买入然后卖出了,第0天第二次重新买入
dp[0][2]=-prices[0];
dp[0][3]=0;
for(int i=1;i<prices.length;i++){
dp[i][0]=Math.max(dp[i-1][0],-prices[i]);
dp[i][1]=Math.max(dp[i-1][1],dp[i-1][0]+prices[i]);
dp[i][2]=Math.max(dp[i-1][2],dp[i-1][1]-prices[i]);
dp[i][3]=Math.max(dp[i-1][3],dp[i-1][2]+prices[i]);
}
return dp[prices.length-1][3];
}
}