121. 买卖股票的最佳时机
dp含义
dp[i][0] 表示第i天持有股票所得最多现金 ,dp[i][1] 表示第i天不持有股票所得最多现金
其实一开始现金是0,那么加入第i天买入股票现金就是 -prices[i], 这是一个负数。
递推公式
第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]);
dp数组如何初始化
由递推公式可以看出基础都是靠dp[0][0]和dp[0][1]推导出来的
那么dp[0][0]表示第0天持有股票,此时的持有股票就一定是买入股票,因为不可能有前一天推出来,所以dp[0][0]=-prices[0]
dp[0][1]表示第0天不持有股票,不持有股票就是dp[0][1]=0
确定遍历顺序
从前往后
推导dp数组
以示例1,输入:[7,1,5,3,6,4]为例,dp数组状态如下:
代码实现
class Solution {
public int maxProfit(int[] prices) {
int N=prices.length;
int[][] dp=new int[N][2];
dp[0][0]=-prices[0];//持有现金的最高现金
dp[0][1]=0;//不持有股票拥有的最高现金
for(int i=1;i<N;i++){
dp[i][0]=Math.max(dp[i-1][0],-prices[i]);
dp[i][1]=Math.max(dp[i-1][1],prices[i]+dp[i-1][0]);
}
return dp[N-1][1];
}
}
122.买卖股票的最佳时机II
与上一题区别是可以多次买卖股票,所以和上一题唯一的区别就是本题股票可以买卖多次了(注意只有一只股票,所以再次购买前要出售掉之前的股票)。
如果第i天持有股票即dp[i][0], 那么可以由两个状态推出来
第i-1天就持有股票,那么就保持现状,所得现金就是昨天持有股票的所得现金 即:dp[i - 1][0]
第i天买入股票,所得现金就是昨天不持有股票的所得现金减去 今天的股票价格 即:dp[i - 1][1] - prices[i]
class Solution {
public int maxProfit(int[] prices) {
int N=prices.length;
int[][] dp=new int[N][2];
dp[0][0]=-prices[0];//持有现金的最高现金
dp[0][1]=0;//不持有股票拥有的最高现金
for(int i=1;i<N;i++){
dp[i][0] =Math. max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
// 注意这里是和121. 买卖股票的最佳时机唯一不同的地方。
dp[i][1]=Math.max(dp[i-1][1],prices[i]+dp[i-1][0]);
}
return dp[N-1][1];
}
}
123.买卖股票的最佳时机III
dp含义
就是同一天可能存在四种状态
确定dp数组以及下标的含义
一天一共就有五个状态,
0没有操作 (其实我们也可以不设置这个状态)
1第一次持有股票
2第一次不持有股票
3第二次持有股票
4第二次不持有股票
dp[i][j]中 i表示第i天,j为 [0 - 4] 五个状态,dp[i][j]表示第i天状态j所剩最大现金。
需要注意:dp[i][1],表示的是第i天,买入股票的状态,并不是说一定要第i天买入股票,这是很多同学容易陷入的误区。
dp数组初始化
dp[0][0]=0
dp[0][1]=-prices[0]
dp[0][2]=0//相当于当天买入当天卖出,没有赚钱,持有现金是0
dp[0][3]=-prices[0] //第二次买入,相当于在第0天,发生买入然后卖出然后又买入
dp[0][4]=0//相当于当天发生了买入,卖出,再买入,再卖出
递推公式
dp[i][1]=Math.max(dp[i-1][1],dp[i-1][0]-price[i])
dp[i][2] = max(dp[i - 1][1] + prices[i], dp[i - 1][2])
dp[i][3]=Math.max(dp[i-1][3],dp[i-1][2]-prices[i])
确定遍历顺序
从递归公式其实已经可以看出,一定是从前向后遍历,因为dp[i],依靠dp[i - 1]的数值。
举例推导dp数组
以输入[1,2,3,4,5]为例
代码实现
class Solution {
public int maxProfit(int[] prices) {
// if(prices.length)
//不操作,第一次买入,第一次卖出,第二次买入,第二次卖出
int N=prices.length;
int[][] dp=new int[N][5];
dp[0][1]=-prices[0];//dp[0][2]=0,dp[0][4]=0
dp[0][3]=-prices[0];
for(int i=1;i<prices.length;i++){
dp[i][0]=dp[i-1][0];
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]);
dp[i][4]=Math.max(dp[i-1][4],dp[i-1][3]+prices[i]);
}
return dp[prices.length-1][4];
}
}
188.买卖股票的最佳时机IV
dp含义
相比于123题就是可以买入卖出k次,所以思路基本是一样的,只是要加一些循环
使用二维数组 dp[i][j] :第i天的状态为j,所剩下的最大现金是dp[i][j]
j的状态表示为:
0 表示不操作
1 第一次买入
2 第一次卖出
3 第二次买入
4 第二次卖出
.....
除了0以外,偶数就是卖出,奇数就是买入
在初始化的地方同样要类比j为偶数是卖、奇数是买的状态。
确定遍历顺序
从递归公式其实已经可以看出,一定是从前向后遍历,因为dp[i],依靠dp[i - 1]的数值。
举例推导dp数组
以输入[1,2,3,4,5],k=2为例。
代码实现
这道题和上一题唯一的区别就是在初始化和递推公式的区别
class Solution {
public int maxProfit(int k, int[] prices) {
// if(prices.length)
//不操作,第一次买入,第一次卖出,第二次买入,第二次卖出
int N=prices.length;
int[][] dp=new int[N][2*k+1];
//初始化
for(int i=1;i<2*k;i+=2){
dp[0][i]=-prices[0];
}
// dp[0][1]=-prices[0];//dp[0][2]=0,dp[0][4]=0
// dp[0][3]=-prices[0];
for(int i=1;i<prices.length;i++){
for(int j=1;j<2*k;j+=2){
dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-1]-prices[i]);//第一次买入
dp[i][j+1]=Math.max(dp[i-1][j+1],dp[i-1][j]+prices[i]);//第一次卖出
}
// dp[i][0]=dp[i-1][0];
// 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[prices.length-1][2*k];
}
}