引言:炒股不易,DP来助!
"股市有风险,入市需谨慎!" 这句话想必大家耳熟能详。但今天,我们不聊那些高深莫测的金融知识,也不预测明天是涨是跌,我们来聊点更"实在"的------如何用计算机科学的"魔法"------动态规划,来模拟股票交易,并从中获取最大利润!
你可能会想,这跟炒股有啥关系?别急,虽然我们不能真的靠算法在股市里一夜暴富(那可是违法的!),但这些股票买卖问题却是算法面试中的"常客",也是理解动态规划思想的绝佳案例。它们就像一个个精心设计的"小游戏",让你在解决问题的过程中,体会到DP的精妙之处。本文将带你领略六道经典的LeetCode股票DP问题,从简单到复杂,层层递进。我们将一起揭开它们的面纱,深入探讨其背后的动态规划思想,并为你提供清晰的解题思路和代码实现。更棒的是,我们还会为你准备一份"武功秘籍"------对比表格,让你一眼看穿这些问题的异同。🚀
动态规划基础:DP是个啥?
在正式"炒股"之前,我们得先搞清楚我们的"交易工具"------动态规划。动态规划,听起来是不是有点高大上?其实,它就是一种解决复杂问题的"分而治之"策略,只不过它更强调"记忆化"和"最优子结构"。
简单来说,当一个问题可以被分解成若干个相互重叠的子问题,并且通过解决这些子问题,最终能够得到原问题的最优解时,动态规划就派上用场了。它会把每个子问题的解都"记住",下次再遇到相同的子问题时,就直接从"记忆库"里取出来,避免重复计算,大大提高效率。这就像你玩游戏打怪升级,每次打完一个怪,都会把它的经验值和掉落物品记录下来,下次再遇到同款怪,你就知道怎么打最省力了。是不是很形象?😂
在股票买卖问题中,我们通常会定义dp
数组来表示在某个时间点、某种状态下的最大利润。例如,dp[i][0]
可能表示第i
天"持有股票"时的最大利润,而dp[i][1]
则表示第i
天"不持有股票"时的最大利润。通过不断地"推导"和"更新"这些状态,我们最终就能找到在整个交易周期内的最大利润。是不是有点小期待了?那就让我们赶紧进入实战环节吧!
经典股票问题逐个击破
1. 买卖股票的最佳时机 (LeetCode 121)
问题描述:
给定一个数组 prices
,它的第 i
个元素 prices[i]
表示一支给定股票第 i
天的价格。你只能选择 某一天买入这只股票,并在未来的某一天卖出 这只股票。设计一个算法来计算你所能获取的最大利润。注意:你不能在买入前卖出股票。比如,你不能在第二天买入,第一天卖出。这不符合逻辑嘛!🤔
动态规划思路
这道题是股票系列中最简单的一道,因为它只允许我们进行 一次交易。我们可以用动态规划来解决,但其实用贪心算法会更直观、更高效。
我们可以定义 dp[i][0]
表示第 i
天持有股票的最大利润,dp[i][1]
表示第 i
天不持有股票的最大利润。
-
dp[i][0]
:- 如果第
i
天选择持有股票,那么可能是前一天就持有(dp[i-1][0]
),或者今天买入(-prices[i]
)。由于只能买入一次,所以今天买入的利润就是-prices[i]
。 - 因此,
dp[i][0] = Math.max(dp[i-1][0], -prices[i])
。
- 如果第
-
dp[i][1]
:- 如果第
i
天选择不持有股票,那么可能是前一天就不持有(dp[i-1][1]
),或者今天卖出(dp[i-1][0] + prices[i]
)。 - 因此,
dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0] + prices[i])
。
- 如果第
初始化:dp[0][0] = -prices[0]
(第一天买入股票),dp[0][1] = 0
(第一天不持有股票)。 最终结果就是 dp[len-1][1]
。
贪心思路(更优解)
既然只能进行一次交易,我们只需要找到历史最低点买入,然后在最高点卖出即可。但这个最高点必须在最低点之后。所以,我们可以维护一个变量 minPrice
记录当前遍历到的最低价格,再维护一个变量 maxProfit
记录当前的最大利润。遍历数组,每次更新 minPrice
,然后用当前价格减去 minPrice
来更新 maxProfit
。这就像你在菜市场买菜,总是想在菜价最低的时候买入,然后在菜价最高的时候卖出,赚个差价。💰
代码示例
js
// 动态规划解法
var maxProfit_dp = function(prices) {
const len = prices.length;
if (len <= 1) return 0;
let dp = new Array(len).fill(0).map(() => new Array(2).fill(0));
dp[0][0] = -prices[0]; // 第0天持有股票
dp[0][1] = 0; // 第0天不持有股票
for (let 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];
};
// 贪心解法
var maxProfit_greedy = function(prices) {
let minPrice = Infinity; // 记录历史最低价格
let maxProfit = 0; // 记录最大利润
for (let i = 0; i < prices.length; i++) {
minPrice = Math.min(minPrice, prices[i]); // 更新最低价格
maxProfit = Math.max(maxProfit, prices[i] - minPrice); // 更新最大利润
}
return maxProfit;
};
2. 买卖股票的最佳时机 II (LeetCode 122)
问题描述:
给定一个数组 prices
,它的第 i
个元素 prices[i]
表示一支给定股票第 i
天的价格。你可以尽可能多地完成交易(多次买卖一支股票)。注意:你不能同时参与多笔交易(即,在再次购买前,你必须卖出之前的股票)。比如,你今天买了,明天卖了,后天又可以买。这就像是"短线操作",只要有钱赚,就赶紧出手!🚀
动态规划思路
这道题与上一道最大的不同在于,我们可以进行 多次交易。这听起来好像更复杂了,但实际上,它反而变得更简单了!
我们仍然可以使用动态规划。定义 dp[i][0]
表示第 i
天持有股票的最大利润,dp[i][1]
表示第 i
天不持有股票的最大利润。
-
dp[i][0]
:- 如果第
i
天选择持有股票,那么可能是前一天就持有(dp[i-1][0]
),或者今天买入(dp[i-1][1] - prices[i]
)。注意,这里买入的利润是dp[i-1][1] - prices[i]
,因为我们可以从前一天不持有股票的状态买入。 - 因此,
dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] - prices[i])
。
- 如果第
-
dp[i][1]
:- 如果第
i
天选择不持有股票,那么可能是前一天就不持有(dp[i-1][1]
),或者今天卖出(dp[i-1][0] + prices[i]
)。 - 因此,
dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0] + prices[i])
。
- 如果第
初始化:dp[0][0] = -prices[0]
,dp[0][1] = 0
。 最终结果是 dp[len-1][1]
。
贪心思路(更优解)
由于可以进行多次交易,我们只需要抓住每一次上涨的机会。如果今天的价格比昨天的价格高,那么我们就在昨天买入,今天卖出,赚取这部分差价。这就像是"积少成多",每次赚一点,最终也能积累不少利润。这种策略之所以可行,是因为多次交易的利润可以分解为每次独立交易的利润之和。例如,[1, 2, 3, 4]
,利润是 (2-1) + (3-2) + (4-3) = 3
,这等价于 4-1 = 3
。所以,只要有利润,就大胆地赚!🤑
代码示例
js
// 动态规划解法
var maxProfit_dp_ii = function(prices) {
const len = prices.length;
if (len <= 1) return 0;
let dp = new Array(len).fill(0).map(() => new Array(2).fill(0));
dp[0][0] = -prices[0];
dp[0][1] = 0;
for (let 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];
};
// 贪心解法
var maxProfit_greedy_ii = function(prices) {
let totalProfit = 0;
for (let i = 1; i < prices.length; i++) {
if (prices[i] > prices[i - 1]) {
totalProfit += prices[i] - prices[i - 1];
}
}
return totalProfit;
};
3. 买卖股票的最佳时机 III (LeetCode 123)
问题描述:
给定一个数组 prices
,它的第 i
个元素 prices[i]
表示一支给定股票第 i
天的价格。你最多可以完成 两笔交易。注意:你不能同时参与多笔交易(即,在再次购买前,你必须卖出之前的股票)。这就像是"限量版"交易,机会只有两次,可得好好把握!💰💰
动态规划思路
这道题的难度在于"最多两笔交易"的限制。这意味着我们不能像第二题那样无脑贪心。我们需要更精细地定义状态。
我们可以定义五种状态来表示不同交易阶段的最大利润:
dp[i][0]
:第i
天不进行任何操作(或者说,没有进行任何交易)的最大利润。始终为0。dp[i][1]
:第i
天第一次持有股票的最大利润。dp[i][2]
:第i
天第一次不持有股票(即完成第一次交易)的最大利润。dp[i][3]
:第i
天第二次持有股票的最大利润。dp[i][4]
:第i
天第二次不持有股票(即完成第二次交易)的最大利润。
状态转移方程:
dp[i][0] = 0
(或者dp[i-1][0]
):不操作,利润为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])
:第二次不持有股票,可能是前一天就不持有,或者今天卖出(从第二次持有状态卖出)。
初始化:
dp[0][0] = 0
dp[0][1] = -prices[0]
(第一次买入)dp[0][2] = 0
(第一次卖出,还没发生,所以是0)dp[0][3] = -prices[0]
(第二次买入,还没发生,所以是第一次买入的利润)dp[0][4] = 0
(第二次卖出,还没发生,所以是0)
最终结果是 dp[len-1][4]
,因为我们希望完成两笔交易并获得最大利润。
代码示例
js
var maxProfit_iii = function(prices) {
const len = prices.length;
if (len === 0) return 0;
// dp[i][0]: 不操作
// dp[i][1]: 第一次持有
// dp[i][2]: 第一次不持有
// dp[i][3]: 第二次持有
// dp[i][4]: 第二次不持有
let dp = new Array(len).fill(0).map(() => new Array(5).fill(0));
dp[0][1] = -prices[0];
dp[0][3] = -prices[0];
for (let i = 1; i < len; 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[len - 1][4];
};
4. 买卖股票的最佳时机 IV (LeetCode 188)
问题描述:
给定一个整数数组 prices
,它的第 i
个元素 prices[i]
表示一支给定股票第 i
天的价格。给定一个整数 k
,你可以最多完成 k
笔交易。注意:你不能同时参与多笔交易(即,在再次购买前,你必须卖出之前的股票)。这道题是前面几道题的"集大成者",它把交易次数从固定值变成了变量 k
。感觉像是在玩一个"无限次"的交易游戏,但突然告诉你,你只有 k
次机会!😱
动态规划思路
这道题是 LeetCode 123 的泛化版本。当 k
足够大时(例如 k >= prices.length / 2
),它就退化成了 LeetCode 122(可以进行无限次交易)。因此,我们需要对 k
进行特殊处理。
我们可以定义 dp[i][j][0]
表示第 i
天最多进行 j
次交易且不持有股票的最大利润,dp[i][j][1]
表示第 i
天最多进行 j
次交易且持有股票的最大利润。
-
dp[i][j][0]
:- 如果第
i
天不持有股票,且完成了j
次交易,那么可能是前一天就不持有(dp[i-1][j][0]
),或者今天卖出(dp[i-1][j][1] + prices[i]
)。 - 因此,
dp[i][j][0] = Math.max(dp[i-1][j][0], dp[i-1][j][1] + prices[i])
。
- 如果第
-
dp[i][j][1]
:- 如果第
i
天持有股票,且完成了j
次交易,那么可能是前一天就持有(dp[i-1][j][1]
),或者今天买入(dp[i-1][j-1][0] - prices[i]
)。注意,这里买入时,交易次数j
已经包含了这次买入,所以是从j-1
次交易且不持有股票的状态买入。 - 因此,
dp[i][j][1] = Math.max(dp[i-1][j][1], dp[i-1][j-1][0] - prices[i])
。
- 如果第
初始化:
dp[0][j][0] = 0
(第0天不持有股票,利润为0)dp[0][j][1] = -prices[0]
(第0天持有股票,利润为负的第0天价格)
最终结果是 dp[n-1][k][0]
。
代码示例
js
var maxProfit_iv = function(k, prices) {
const n = prices.length;
if (n === 0 || k === 0) return 0;
// 优化:当k大于n/2时,相当于可以进行无限次交易
// 因为一次买卖至少需要两天,所以最多进行 n/2 次交易
if (k >= Math.floor(n / 2)) {
let profit = 0;
for (let i = 1; i < n; i++) {
if (prices[i] > prices[i - 1]) {
profit += prices[i] - prices[i - 1];
}
}
return profit;
}
// dp[i][j][0]表示第i天最多进行j次交易且不持有股票的最大利润
// dp[i][j][1]表示第i天最多进行j次交易且持有股票的最大利润
let dp = new Array(n).fill(0).map(() => {
return new Array(k + 1).fill(0).map(() => {
return [0, 0];
});
});
// 初始化dp数组
for (let j = 1; j <= k; j++) {
dp[0][j][1] = -prices[0];
}
// 状态转移
for (let i = 1; i < n; i++) {
for (let j = 1; j <= k; j++) {
dp[i][j][0] = Math.max(dp[i - 1][j][0], dp[i - 1][j][1] + prices[i]);
dp[i][j][1] = Math.max(dp[i - 1][j][1], dp[i - 1][j - 1][0] - prices[i]);
}
}
return dp[n - 1][k][0];
};
5. 买卖股票的最佳时机含冷冻期 (LeetCode 309)
问题描述:
给定一个整数数组 prices
,其中 prices[i]
表示第 i
天的股票价格 。设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能多地完成交易(多次买卖一支股票):
- 卖出股票后,你无法在第二天买入股票 (即,冷冻期为 1 天)。
- 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
这就像是"交易暂停"!你刚赚了一笔,正准备乘胜追击,结果系统告诉你:"抱歉,您需要休息一天才能继续交易!" 真是让人又爱又恨的冷冻期啊!🥶
动态规划思路
这道题引入了一个新的状态------"冷冻期"。我们需要在DP状态中体现这个限制。
我们可以定义三种状态:
dp[i][0]
:第i
天持有股票的最大利润。dp[i][1]
:第i
天卖出股票的最大利润(卖出后进入冷冻期)。dp[i][2]
:第i
天处于冷冻期或不持有股票且非冷冻期的最大利润(即可以买入股票的状态)。
状态转移方程:
-
dp[i][0] = Math.max(dp[i-1][0], dp[i-1][2] - prices[i])
:- 第
i
天持有股票,可能是前一天就持有,或者从"可以买入"的状态(dp[i-1][2]
)买入。
- 第
-
dp[i][1] = dp[i-1][0] + prices[i]
:- 第
i
天卖出股票,只能是前一天持有股票,今天卖出。
- 第
-
dp[i][2] = Math.max(dp[i-1][2], dp[i-1][1])
:- 第
i
天处于"可以买入"的状态,可能是前一天就处于这个状态,或者前一天卖出股票后进入冷冻期(dp[i-1][1]
)。
- 第
初始化:
dp[0][0] = -prices[0]
(第一天买入股票)dp[0][1] = 0
(第一天不可能卖出)dp[0][2] = 0
(第一天不持有且非冷冻期)
最终结果是 Math.max(dp[n-1][1], dp[n-1][2])
,因为最终利润肯定是不持有股票的状态。
代码示例
js
var maxProfit_cold_period = function(prices) {
const n = prices.length;
if (n < 2) return 0;
// dp[i][0]: 第i天持有股票的最大利润
// dp[i][1]: 第i天卖出股票的最大利润
// dp[i][2]: 第i天处于冷冻期或不持有股票且非冷冻期的最大利润
let dp = new Array(n).fill(0).map(() => new Array(3).fill(0));
dp[0][0] = -prices[0];
dp[0][1] = 0;
dp[0][2] = 0;
for (let i = 1; i < n; i++) {
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][2] - prices[i]);
dp[i][1] = dp[i - 1][0] + prices[i];
dp[i][2] = Math.max(dp[i - 1][2], dp[i - 1][1]);
}
return Math.max(dp[n - 1][1], dp[n - 1][2]);
};
6. 买卖股票的最佳时机含手续费 (LeetCode 714)
问题描述:
给定一个整数数组 prices
,其中 prices[i]
表示第 i
天的股票价格 ;整数 fee
代表了交易手续费用。你可以尽可能多地完成交易。注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。每次交易你都需要支付 fee
的手续费。这就像是"隐形税收"!每次交易都要被"抽成",这可得好好算算,别忙活半天,利润全交手续费了!💸
动态规划思路
这道题与 LeetCode 122 类似,都是可以进行多次交易。唯一的区别在于,每次卖出股票时,需要支付一笔手续费。
我们仍然定义两种状态:
dp[i][0]
:第i
天持有股票的最大利润。dp[i][1]
:第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] - fee)
:- 第
i
天不持有股票,可能是前一天就不持有,或者从持有状态卖出。注意,这里卖出时需要减去手续费fee
。
- 第
初始化:
dp[0][0] = -prices[0]
(第一天买入股票)dp[0][1] = 0
(第一天不持有股票)
最终结果是 dp[n-1][1]
。
代码示例
js
var maxProfit_fee = function(prices, fee) {
const n = prices.length;
if (n === 0) return 0;
// dp[i][0]: 第i天持有股票的最大利润
// dp[i][1]: 第i天不持有股票的最大利润
let dp = new Array(n).fill(0).map(() => new Array(2).fill(0));
dp[0][0] = -prices[0];
dp[0][1] = 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][0] + prices[i] - fee);
}
return dp[n - 1][1];
};
股票DP问题对比分析:异同点一览
经过上面六道题的"洗礼",你是不是对股票DP问题有了更深入的理解?为了让你一眼看穿它们的"庐山真面目",我特意为你准备了一份"武功秘籍"------对比表格!请看:
题目 | 交易次数限制 | 特殊条件 | DP状态数 | 核心思想 | 时间复杂度 | 空间复杂度 |
---|---|---|---|---|---|---|
121. 买卖股票的最佳时机 | 最多1次 | 无 | 2个状态 | 记录最低价格和最大利润 | O(n) | O(1) |
122. 买卖股票的最佳时机 II | 无限次 | 无 | 2个状态 | 贪心:每次上涨都交易 | O(n) | O(1) |
123. 买卖股票的最佳时机 III | 最多2次 | 无 | 5个状态 | 分别记录两次交易的状态 | O(n) | O(1) |
188. 买卖股票的最佳时机 IV | 最多k次 | k可能很大时退化为无限次交易 | 2k+1个状态 | 通用k次交易模板 | O(nk) | O(k) |
309. 买卖股票的最佳时机含冷冻期 | 无限次 | 卖出后冷冻1天 | 3-4个状态 | 增加冷冻期状态 | O(n) | O(1) |
714. 买卖股票的最佳时机含手续费 | 无限次 | 每次交易有手续费 | 2个状态 | 卖出时扣除手续费 | O(n) | O(1) |
从这个表格中,我们可以清晰地看到:
- 交易次数限制 是区分这些问题的关键。从"一次"到"无限次"再到"k次",DP状态的定义也随之变化。
- 特殊条件(冷冻期、手续费)会增加额外的状态维度或修改状态转移方程。
- DP状态数 随着问题的复杂性而增加,但核心思想都是围绕"持有"和"不持有"股票这两种基本状态展开。
- 时间复杂度和空间复杂度 在很多情况下都可以通过"滚动数组"等优化手段降低,实现O(1)的空间复杂度。
是不是感觉豁然开朗了?😎
动态规划思想升华:从股票问题看DP的本质
经过这六道股票问题的"实战演练",你是否对动态规划有了更深刻的理解?这些问题虽然形式各异,但其背后都蕴含着动态规划的精髓。总结一下,动态规划在解决这类问题时,通常遵循以下"三板斧":
确定状态
这是动态规划的第一步,也是最关键的一步。你需要明确 dp
数组的含义,它代表了什么?在股票问题中,dp[i][j]
通常表示在第 i
天,处于某种状态 j
时的最大利润。状态的定义要能够覆盖所有可能的情况,并且能够方便地进行状态转移。比如,我们不仅要考虑"持有"和"不持有"股票,还要考虑"交易次数"、"冷冻期"等特殊条件,把它们都融入到状态定义中。
确定状态转移方程
状态转移方程是动态规划的"灵魂"。它描述了如何从已知的子问题解推导出当前问题的解。在股票问题中,状态转移方程通常是 max
函数的组合,表示在当前状态下,我们可以通过哪些前一天的状态转移过来,并选择其中利润最大的。这就像是在做选择题,每一步都要选择最优的路径,才能最终达到最大利润的"彼岸"。
确定初始化和遍历顺序
初始化是为 dp
数组的边界条件赋值,为后续的状态转移提供"起点"。遍历顺序则决定了我们计算 dp
数组的先后次序,确保在计算 dp[i]
时,所有依赖的 dp[i-1]
或其他更早的状态都已经计算完毕。在股票问题中,通常是从第0天开始,逐天向后遍历。
DP核心思想提炼
从股票问题中,我们还能提炼出DP的几个核心思想:
- 最优子结构: 股票问题的最大利润,可以通过子问题的最大利润推导出来。比如,今天的最大利润,取决于昨天的最大利润加上今天的操作。
- 无后效性: 今天的决策只影响未来的状态,而不会影响过去的状态。我们不需要关心过去是如何达到某个状态的,只需要知道那个状态的最大利润是多少。
- 重叠子问题: 很多子问题会被重复计算。动态规划通过"记忆化"或填充
dp
表的方式,避免了重复计算,大大提高了效率。
所以,当你下次再遇到类似的优化问题时,不妨想想动态规划的这"三板斧",也许就能找到解题的突破口。动态规划不仅仅是一种算法,更是一种解决问题的思维方式。掌握了它,你就能在算法的海洋里,乘风破浪,所向披靡!🌊
结语:DP在手,天下我有!
恭喜你!🎉 经过这番"股票DP"的深度学习,你已经掌握了解决六道经典股票买卖问题的动态规划方法。从最初的"一次交易"到复杂的"k次交易"、"冷冻期"和"手续费",我们一步步揭示了这些问题背后的DP奥秘。你不仅学会了如何定义状态、推导状态转移方程,更重要的是,你体会到了动态规划在解决这类问题时的强大之处。
动态规划就像一把瑞士军刀,虽然看起来复杂,但一旦掌握了它的使用方法,就能解决各种各样的问题。在算法的世界里,它无处不在,从背包问题到最长公共子序列,再到我们今天探讨的股票问题,DP的身影随处可见。希望通过本文,你不仅能轻松应对面试中的股票DP问题,更能将这种"化繁为简,步步为营"的思维方式运用到你解决其他问题的过程中。
题目链接
1\] LeetCode 121. 买卖股票的最佳时机: [leetcode.cn/problems/be...](https://link.juejin.cn?target=https%3A%2F%2Fleetcode.cn%2Fproblems%2Fbest-time-to-buy-and-sell-stock%2F "https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/") \[2\] LeetCode 122. 买卖股票的最佳时机 II: [leetcode.cn/problems/be...](https://link.juejin.cn?target=https%3A%2F%2Fleetcode.cn%2Fproblems%2Fbest-time-to-buy-and-sell-stock-ii%2F "https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/") \[3\] LeetCode 123. 买卖股票的最佳时机 III: [leetcode.cn/problems/be...](https://link.juejin.cn?target=https%3A%2F%2Fleetcode.cn%2Fproblems%2Fbest-time-to-buy-and-sell-stock-iii%2F "https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iii/") \[4\] LeetCode 188. 买卖股票的最佳时机 IV: [leetcode.cn/problems/be...](https://link.juejin.cn?target=https%3A%2F%2Fleetcode.cn%2Fproblems%2Fbest-time-to-buy-and-sell-stock-iv%2F "https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv/") \[ 5\] LeetCode 309. 买卖股票的最佳时机含冷冻期: [leetcode.cn/problems/be...](https://link.juejin.cn?target=https%3A%2F%2Fleetcode.cn%2Fproblems%2Fbest-time-to-buy-and-sell-stock-with-cooldown%2F "https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-cooldown/") \[6\] LeetCode 714. 买卖股票的最佳时机含手续费: [leetcode.cn/problems/be...](https://link.juejin.cn?target=https%3A%2F%2Fleetcode.cn%2Fproblems%2Fbest-time-to-buy-and-sell-stock-with-transaction-fee%2F "https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/")