121. 买卖股票的最佳时机
思路:这个题可以用贪心的方法来做,找到最左最小的值,取最右最大值,找到最大的利润。
代码如下:
class Solution {
public int maxProfit(int[] prices) {
int low=Integer.MAX_VALUE;
int result=0;
for(int i=0;i<prices.length;i++){
low=Math.min(low,prices[i]);
result=Math.max(result,prices[i]-low);
}
return result;
}
}
考虑动态规划
动规五部曲分析如下:
1、确定dp数组以及下标的含义
dp[i][0] 表示第i天持有股票所得最多现金,一开始现金是0,那么加入第i天买入股票现金就是 -prices[i], 是一个负数。
dp[i][1] 表示第i天不持有股票所得最多现金
2、确定递推公式
如果第i天持有股票即dp[i][0],那么可以由两个状态推出来
- 第i-1天就持有股票,那么就保持现状,所得现金就是昨天持有股票的所得现金 即:dp[i - 1][0]
- 第i天买入股票,所得现金就是买入今天的股票后所得现金即:-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]);
3、dp数组如何初始化
递推公式要从dp[0][0]和dp[0][1]推导出来,dp[0][0]表示第0天持有股票,dp[0][0] = -prices[0]; dp[0][1]表示第0天不持有股票,dp[0][1] = 0;
4、确定遍历顺序
dp[i]是由dp[i - 1]推导出来的,从前向后遍历。
5、举例推导dp数组
代码如下:
class Solution {
public int maxProfit(int[] prices) {
int len=prices.length;
if(len==0) return 0;
int[][] dp=new int[len][2];
dp[0][0]-=prices[0];
dp[0][1]=0;
for(int i=1;i<len;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]);
}
return dp[len-1][1];
}
}
122.买卖股票的最佳时机II
思路:这个题之前在贪心算法中做过,因为可以随时买入和卖出,所以只需要累加所有的正利润即可。
考虑动规的做法,这一题跟上一题的差别在于可以随时买入和卖出,主要区别在于持有股票的递推公式发生了变化。
如果第i天持有股票即dp[i][0],那么可以由两个状态推出来
- 第i-1天就持有股票,那么就保持现状,所得现金就是昨天持有股票的所得现金 即:dp[i - 1][0]
- 第i天买入股票,所得现金就是昨天不持有股票所得现金减去今天买入的股票后所得现金即:dp[i - 1][1]-prices[i]
dp[i][0]应该选所得现金最大的,所以dp[i][0] = max(dp[i - 1][0], -prices[i]);
代码如下:
class Solution {
public int maxProfit(int[] prices) {
int len=prices.length;
if(len==0) return 0;
int[][] dp=new int[len][2];
dp[0][0]-=prices[0];
dp[0][1]=0;
for(int i=1;i<len;i++){
dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]-prices[i]);
dp[i][1]=Math.max(dp[i-1][1],dp[i-1][0]+prices[i]);
}
return dp[len-1][1];
}
}
123.买卖股票的最佳时机III
思路:本题限制了最多只能完成两笔交易。
动规五部曲:
1、确定dp数组以及下标的含义
一天一共就有五个状态,
0-没有操作
1-第一次持有股票
2-第一次不持有股票
3-第二次持有股票
4-第二次不持有股票
dp[i][j]中 i表示第i天,j为 [0 - 4] 五个状态,dp[i][j]表示第i天状态j所剩最大现金。
2、确定递推公式
达到dp[i][1]状态,有两个具体操作:
操作一:第i天买入股票了,那么dp[i][1] = dp[i-1][0] - prices[i]
操作二:第i天没有操作,而是沿用前一天买入的状态,即:dp[i][1] = dp[i - 1][1]
所以dp[i][1] = max(dp[i-1][0] - prices[i], dp[i - 1][1]);
同理dp[i][2]也有两个操作:
操作一:第i天卖出股票了,那么dp[i][2] = dp[i - 1][1] + prices[i]
操作二:第i天没有操作,沿用前一天卖出股票的状态,即:dp[i][2] = dp[i - 1][2]
所以dp[i][2] = max(dp[i - 1][1] + prices[i], dp[i - 1][2])
同理可推出剩下状态部分:
dp[i][3] = max(dp[i - 1][3], dp[i - 1][2] - prices[i]);
dp[i][4] = max(dp[i - 1][4], dp[i - 1][3] + prices[i]);
3、dp数组如何初始化
第0天没有操作,这个最容易想到,就是0,即:dp[0][0] = 0;
第0天做第一次买入的操作,dp[0][1] = -prices[0];
第0天做第一次卖出的操作,可以理解当天买入,当天卖出,所以dp[0][2] = 0;
第0天第二次买入操作,相当于第0天第一次买入了,第一次卖出了,然后再买入一次(第二次买入),那么现在手头上没有现金,只要买入,现金就做相应的减少。所以第二次买入操作,初始化为:dp[0][3] = -prices[0];
同理第二次卖出初始化dp[0][4] = 0;
4、确定遍历顺序
从递归公式可以看出从前向后遍历,因为dp[i],依靠dp[i - 1]的数值。
5、举例推导dp数组
代码如下:
class Solution {
public int maxProfit(int[] prices) {
int len=prices.length;
if(len==0) return 0;
int[][] dp=new int[len][5];
dp[0][1]=0-prices[0];
dp[0][3]=0-prices[0];
for(int i=1;i<len;i++){
dp[i][1]=Math.max(dp[i-1][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]);
dp[i][4]=Math.max(dp[i-1][4],dp[i-1][3]+prices[i]);
}
return dp[len-1][4];
}
}