"给血雨腥风的二级市场留下八个大字——巴菲特就那么回事"
题目链接:309. 最佳买卖股票时机含冷冻期,给定一个整数数组 prices,其中 prices[i] 表示第 i 天的股票价格 。设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票)。卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
输入:
输出:
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]
该题目是 122. 买卖股票的最佳时机2 的延伸,交易次数不限,但是交易后会有一天的冷冻期(这一天只能静静地度过,不能再买入)。
仔细思考,当天是不是“冷冻期”和什么有关?答案:仅与前一天是否卖出有关。所以“冷冻期”是一个幌子,我们只需要区分不持股状态下是否有卖出行为即可,即如果有卖出行为,则次日不能买入。
方法1: 中规中矩的动态规划
1、确定 dp 数组以及含义
由于不能同时参与多笔交易,即必须卖出手中股票才能再次购买,或手中不持股时才能买入,因此每天交易结束后仅有两种情况,即,1、手中持股,2、手中不持股。
我们将手中不持股状态再细分,可以分为当前没卖出与当天有卖出,故整体状态可分为三种,即, 1、手中持股,2、手中不持股,且没卖出(次日可以自由交易),3、手中不持股,且有卖出(次日只能乖乖等待)。
故,用动态规划数组 表示第 日持股状态为 时,我们手上最大的利润(因为是“空手套白狼“的融资炒股,即手上没有本钱,只要交易结束后,手上的现金即为收益),其中,
-
-
分别为,持股状态 、不持股且没卖出 、不持股且有卖出
2、确定 dp 对应的状态方程
对于 状态共有三种状态需要讨论并计算,即,
-
:第 天在持股状态下的最大利润
-
:第 天在不持股且当天没卖出状态下的最大利润
-
:第 天在我们不持股且当天有卖出状态下的最大利润
:第 天我们持有股票,存在两种可能,即
-
第 天持股,即
-
第 天不持股且当天没卖出(因为第 天一旦卖出了,第 天就不能买入了),在第 天买入了,即
故,
:第 天我们没有股票,而且并不是因为卖了才没有的,所以从第 天向第 天转移时,存在两种可能,即,
-
第 天也不持股且当天没卖出,即
-
第 天也不持股且当天有卖出,即
故,
:第 天我们卖出了股票,说明前一天必须持有,故
3、确定 dp 初始状态
-
第 天我们持股状态下的最大收益:
-
第 天我们不持股且当前没卖出状态下的最大收益:
-
第 天我们不持股且当前有卖出状态下的最大收益:
4、确定遍历顺序
从第 天遍历到第 天
5、确定最终返回值
第 天如还持有股票,即 ,收益肯定不是最大的,所以仅需要比较 数组的第1、2项元素大小即可,故返回值为
6、上代码
中规中矩
/**
* 空间复杂度 O(n),n是prices数组的长度
* 时间复杂度 O(n)
*/
function maxProfit(prices: number[]): number {
const n = prices.length;
const dp = Array.from({ length: n}, () => [0, 0, 0]);
dp[0][0] = -prices[0];
for(let i = 1; i < n; 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][2]);
dp[i][2] = dp[i - 1][0] + prices[i];
}
return Math.max(dp[n - 1][1], dp[n - 1][2]);
};
状态压缩
/**
* 空间复杂度 O(1)
* 时间复杂度 O(n)
*/
function maxProfit(prices: number[]): number {
let dp0 = -prices[0];
let dp1 = 0;
let dp2 = 0;
for(let i = 1, len = prices.length; i < len; i++) {
[dp0, dp1,dp2 ] = [
Math.max(dp0, dp1 - prices[i]),
Math.max(dp1, dp2),
dp0 + prices[i],
]
}
return Math.max(dp1, dp2);
};
方法2: 思路进阶
冷冻期是一个干扰概念,实际上还是限制交易时机(即买入、卖出)。我们只需要关心第 天手上有木有股票即可。
1、确定 dp 数组以及含义
用动态规划数组 表示第 日持股状态为 时的最大收益,其中,
-
-
,分别为,没持股 、有持股
2、确定 dp 对应的状态方程
对于 状态共有两种状态需要讨论并计算,即,
-
:第 天在我们没持股状态下的最大收益
-
:第 天在我们有持股状态下的最大收益
:第 天我们没有持有股票,存在两种可能,即
-
第 天也没持股,即
-
第 天有持股且在次日卖出,即
故,
:第 天我们持有股票,存在两种可能,即,
-
第 天也有持股,即
-
第 天是冷静期,第 天没持股的状态下,当日买入,即 💥
故,
3、确定 dp 初始状态
NOTE: 状态转移方程中涉及到了 ,故在初始化时要考虑交易前两天的情况,即 、。
-
第 天我们不持股状态下的最大收益,
-
第 天我们持股状态下的最大收益,
-
第 天我们不持股状态下的最大收益(只是第 天的股价大于第 天的股价,才有正收益,否则为 ),
-
第 天我们持股状态下的最大收益(第 天与第 天只有一次买入机会,选择价格较小的那一天买入即可),
4、确定遍历顺序
从第 天遍历到第 天
5、确定最终返回值
第 天还持股肯定不是最大的利润,故返回
6、上代码
/**
* 空间复杂度 O(n),n是prices数组的长度
* 时间复杂度 O(n)
*/
function maxProfit(prices: number[]): number {
const n = prices.length;
if (n < 2) return 0;
const dp = Array.from({ length: n }, () => [0, 0]);
dp[0][0] = 0;
dp[0][1] = -prices[0];
dp[1][0] = Math.max(prices[1] - prices[0], 0);
dp[1][1] = Math.max(-prices[0], -prices[1]);
for(let i = 2; i < n; 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 - 2][0] - prices[i]);
}
return dp[n - 1][0];
};