动态规划来决定最佳时机,这次有冷冻期!| LeetCode:309.买卖股票的最佳时机含冷冻期_哔哩哔哩_bilibili
动态规划来决定最佳时机,这次含手续费!| LeetCode:714.买卖股票的最佳时机含手续费_哔哩哔哩_bilibili
309.最佳买卖股票时机含冷冻期
思路:本题增加了一种冷冻期的量,引出一种全新的状态。我所理解的是本题增加了一个状态,是冷冻状态。代码随想录 对将本题细分为了四个状态。我个人理解上还是认为三个状态更加好理解,但是的确三个状态之中存在一个疑惑,这点我们一会儿来说明。
首先股票类题目可以总结出这样的几个前提:
- dp[i][k] 表示的是当天之后处于什么样的状态且拥有怎样的最大收益,且每天结束时与后一天开始时是等价的;
- 每一天的收益与状态是与依赖前一天所转化的,这不仅是与现实逻辑——要一天天过相通;同时也是保证每一次状态转移是贴合题目逻辑也是可以形成形式化的操作。所以第i天不会涉及到i-2天的内容,而i-2天的结果一定是传递到了i-1天的结果中,然后再传递到i天的结果中;
现在给出三个状态:0表示不持有股票;1表示持有股票;2表示处于冷冻期;
相应的dp[i][0],第i天结束后处于不持有股票状态且不处于冷冻期;dp[i][1]表示结束时处于持有股票状态;dp[i][2]表示今天结束之后处于冷冻期状态且不持有股票。三个状态的值之间转换是这样,注意我们所考虑的i一定是参考i-1及其之前的,比参考i的,所以第i天卖了股票后状态就变成2了,且后一天一定也是2,这一点三种状态的核心争议点的来源,明确这一点后三种状态就比较清晰了。
- dp[i][1] = max(dp[i-1][1], dp[i-1][0]-prices[i],第i天结束后处于持有股票的状态;要么延续买了前一天有股票状态;要么可以买入然后买了股票,这其中包括前一天是0或者前两天是2;
- dp[i][2] = dp[i-1][1]+prices[i],第i天结束之后处于冷冻状态,那么一定只有卖了股票才会出现冷冻期状态。此外这里0和2一定是独立分开的,所以如果卖了股票是处于冷冻期,那么一定不会转换到不是冷冻期的0,所以0状态与1状态之间没有关联的可能。并且冷冻期只会是一天,i-1是冷冻期,则第i天也是冷冻期。
- dp[i][0] = max(dp[i-1][0], dp[i-1][2]) 第i天结束后处于不持有且不冷冻的状态,那么有两种来源,其一是买了股票持有,与不持有相悖,所以0不会从1状态转换过来;所以只会是延续前一天,或者前一天是卖出了股票或者是冷冻期,那么今天结束时就会恢复到可以购买,即后一天可以购买,那么也可以反过来等效为今天结束时冷冻期已经解冻了
// 时间复杂度O(n)
// 空间复杂度O(3*n)
class Solution {
public int maxProfit(int[] prices) {
// 具有三种状态
// dp[i][0] 表示处于不持有股票的状态,且不处于冷冻期
// dp[i][1] 表示持有股票
// dp[i][2] 表示不持有股票且处于冷冻期
int n = prices.length;
int[][] dp = new int[n][3];
dp[0][0] = 0;
dp[0][1] = -prices[0];
dp[0][2] = 0;
for(int i=1; i<n; i++){
// dp[i][0/1/2]表示的都是第i天结束之后的最大收益
// 持有股票,可以是第i-1天结束后就持有股票;也可以是i-1天结束后处于不持有股票且不处于冷冻期在第i天进行了股票买入
if(i > 2)
dp[i][1] = Math.max(dp[i-1][1], Math.max(dp[i-1][0]-prices[i], dp[i-2][2]-prices[i]));
else
dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0]-prices[i]);
// 不持有股票且不是冷冻期,可以是第i-1天结束后是不持有股票且不处于冷冻期,第i天继续不持有;也可以是第i-1天结束后处于了冷冻期,则今天一天处于冷冻期,但是今天结束后就不是冷冻期
dp[i][0] = Math.max(dp[i-1][0], dp[i-1][2]);
// 处于冷冻期,一定是第i天卖出了股票,若第i-1天处于冷冻期,则第i天冷冻期已经结束了
dp[i][2] = dp[i-1][1]+prices[i];
}
return Math.max(dp[n-1][0], dp[n-1][2]);
}
}
如果使用三个状态一定要明确dp[i][k]表示的是当天结束时处于了什么状态,当天的操作已经全部完成了;第二当天结束时的状态是等效与后一天开始时的状态的,即后一天可以做什么操作,在当天结束时就可以做同样的所有操作(比如结束时是2,当前与后一天都是冷冻,但后一天的结束时就是0了,与后两天开始是0保持一致,后两天开始时可以做的所有操作在后一天结束时就已经可以做了;结束时是1,则后一天可以卖,也可以什么都不操作;结束时是0,则后一天可以买入,也可以什么都不操作)
另解
// 时间复杂度O(n)
// 空间复杂度O(2n+2)
class Solution {
public int maxProfit(int[] prices) {
// 买入股票的最佳时机II 的每一步卖出时加入处理手续费的操作即可
int n = prices.length;
// 扩展为n+1是为了更好的处理i-2的操作
int[][] dp = new int[n+1][2];
// 初始化,第0天拥有股票显然是不合法的
dp[0][1] = Integer.MIN_VALUE;
dp[0][0] = 0;
// 注意这里的i是表示天数,而不是数组的索引下标,且初始化时没有使用任一数组内的元素,所以访问数组需要i-1
for(int i=1; i<=n; i++){
if(i>1)
dp[i][1] = Math.max(dp[i-1][1], dp[i-2][0]-prices[i-1]);// 持有状态只会是延续前一天或者是来源于不算冷冻期的最近一次不持有状态,直接减去2,这其中依赖于递推一定是一步一步从头或从尾进行遍历的
else
dp[i][1] = Math.max(dp[i-1][1], -prices[i-1]); // 第一天结束处于持有状态,并且更新为合法的值
dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1]+prices[i-1]);
}
return dp[n][0];
}
}
714.买卖股票的最佳时机含手续费
思路:就是在 122.买卖股票的最佳时机II 中每次卖出股票时增加手续费的操作即可求解。
// 时间复杂度O(n)
// 空间复杂度O(2*n)
class Solution {
public int maxProfit(int[] prices, int fee) {
// 买入股票的最佳时机II 的每一步卖出时加入处理手续费的操作即可
int n = prices.length;
int[][] dp = new int[n][2];
dp[0][1] = -prices[0];
dp[0][0] = 0;
for(int i=1; i<n; i++){
dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0]-prices[i]);
dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1]+prices[i]-fee);
}
return dp[n-1][0];
}
}