LeetCode 188:买卖股票的最佳时机 IV

LeetCode 188:买卖股票的最佳时机 IV

1. 题目核心

给定股票每天的价格 prices[i] 和整数 k,最多进行 k 笔完整交易,每笔交易必须是:

text 复制代码
买入 → 卖出

并且必须卖掉手里的股票之后才能再次买入。注意题目说的是最多 k ,不是必须交易 k 次,所以可以交易 0、1、2...k 次。


2. 思路一:枚举所有买卖方案

最直观的方法是枚举:

text 复制代码
第一次哪天买 → 哪天卖
第二次哪天买 → 哪天卖
......

例如:

text 复制代码
prices = [3,2,6,5,0,3]
k = 2

可以枚举出:

text 复制代码
2 买 → 6 卖,利润 4
0 买 → 3 卖,利润 3
总利润 = 7

这种方法的问题是组合数量非常多。第一笔交易确定以后还要继续枚举第二笔、第三笔,因此会出现大量重复计算,不适合直接实现。


3. 思路二:最直观的动态规划

3.1 DP 状态

定义:

text 复制代码
dp[t][i]

表示:

在第 0~i 天中,最多完成 t 笔交易时能够获得的最大利润。

例如:

text 复制代码
dp[2][5]

表示:

前 6 天里最多完成 2 笔交易能够获得的最大利润。

3.2 到第 i 天时只有两种情况

对于:

text 复制代码
dp[t][i]

第一种情况:今天不卖股票。那么最大利润就是昨天已经得到的:

text 复制代码
dp[t][i - 1]

第二种情况:今天卖股票 。假设第 j 天买入,第 i 天卖出,这一笔交易的利润为:

text 复制代码
prices[i] - prices[j]

但是在第 j 天买入之前,还可以已经完成最多 t-1 笔交易,所以总利润为:

text 复制代码
前面 t-1 笔交易的最大利润
+
当前这一笔交易的利润

即:

text 复制代码
dp[t-1][j-1] + prices[i] - prices[j]

所以完整公式为:

text 复制代码
dp[t][i]
=
max(
    dp[t][i-1],
    dp[t-1][j-1] + prices[i] - prices[j]
)

由于买入日 j 不确定,因此实际上还需要把前面所有可能的 j 都尝试一遍:

text 复制代码
dp[t][i]
=
max(
    dp[t][i-1],
    max(
        dp[t-1][j-1] + prices[i] - prices[j]
    )
)

3.3 max 到底在比较什么

它并不抽象,本质就是:

text 复制代码
以前已经找到的最好方案
vs
今天卖掉产生的新方案

谁利润高,就保留谁。

例如:

text 复制代码
prices = [3,2,6,5]

到价格 6 时:

text 复制代码
2 买 → 6 卖 = 4

所以最大利润变成 4

第二天价格为 5

text 复制代码
以前最好利润 = 4
今天卖 = 5 - 2 = 3

所以:

text 复制代码
max(4,3) = 4

说明今天不卖更好。

3.4 这个 DP 的问题

i 要遍历所有天,t 要遍历所有交易次数,还需要枚举买入日 j

text 复制代码
时间复杂度:O(k × n²)

可以继续优化。


4. 最优思路:buy / sell 状态动态规划

把每一笔交易拆成两个状态:

text 复制代码
第1次买 → 第1次卖 → 第2次买 → 第2次卖 → ...

对于第 t 笔交易定义:

text 复制代码
buy[t]  = 第 t 次买入以后,目前能够保留的最好收益状态
sell[t] = 第 t 次卖出以后,目前能够获得的最大利润

例如 k = 2

text 复制代码
buy[1] → sell[1] → buy[2] → sell[2]

5. 两个核心公式

5.1 买入公式

text 复制代码
buy[t] = max(
    buy[t],
    sell[t-1] - price
)

含义:

text 复制代码
方案1:今天不买
       → 保留以前的 buy[t]

方案2:今天买
       → 前 t-1 笔交易已经赚的钱 - 今天买股票花的钱

所以:

text 复制代码
buy[t] = max(以前买的最好状态, 今天买的状态)

例如第二次买:

text 复制代码
sell[1] = 4
price = 0

表示第一笔已经赚了 4,现在价格为 0

text 复制代码
buy[2] = 4 - 0 = 4

也就是:

text 复制代码
第一笔交易赚4
→ 0元买入第二次股票
→ 当前整体收益状态仍然为4

5.2 卖出公式

text 复制代码
sell[t] = max(
    sell[t],
    buy[t] + price
)

含义:

text 复制代码
方案1:今天不卖
       → 保留以前的 sell[t]

方案2:今天卖
       → 第 t 次买入后的状态 + 今天卖股票得到的钱

所以:

text 复制代码
sell[t] = max(以前卖出的最大利润, 今天卖出的利润)

6. 两条公式如何衔接

整个过程可以直接画成:

text 复制代码
sell[t-1]
    ↓
前 t-1 笔交易已经获得的利润
    ↓
减去今天股价
    ↓
buy[t]
    ↓
以后加上某一天的股价卖出
    ↓
sell[t]

所以核心只有:

text 复制代码
buy[t]  = max(buy[t], sell[t-1] - price)
sell[t] = max(sell[t], buy[t] + price)

其中所有 max 都是在比较:

text 复制代码
以前最好的结果
vs
今天进行操作得到的新结果

7. 示例完整推导

text 复制代码
prices = [3,2,6,5,0,3]
k = 2

状态:

text 复制代码
buy1  sell1  buy2  sell2

每天更新后的结果:

当前价格 buy1 sell1 buy2 sell2
3 -3 0 -3 0
2 -2 0 -2 0
6 -2 4 -2 4
5 -2 4 -1 4
0 0 4 4 4
3 0 4 4 7

关键位置是价格 6

text 复制代码
buy1 = -2
sell1 = -2 + 6 = 4

表示:

text 复制代码
2 买 → 6 卖
第一笔赚 4

后来价格变成 0

text 复制代码
buy2 = sell1 - 0
     = 4

表示:

text 复制代码
第一笔已经赚 4
然后 0 元进行第二次买入

最后价格为 3

text 复制代码
sell2 = buy2 + 3
      = 4 + 3
      = 7

最终:

text 复制代码
2 → 6,赚4
0 → 3,赚3
总利润 = 7

8. 为什么不用一定交易 k 次

题目要求:

text 复制代码
最多 k 次

而不是:

text 复制代码
必须 k 次

例如:

text 复制代码
prices = [10,8,6,4,2]

无论 k 多大,最优方案都是:

text 复制代码
一次都不交易
利润 = 0

如果最后一天才是最高价,也完全没问题。例如:

text 复制代码
prices = [3,2,4,5,100]

buy[1] 会一直保留最好的买入状态:

text 复制代码
2 元买入

到最后一天:

text 复制代码
sell[1] = -2 + 100 = 98

因此 DP 并不会提前强制卖出。


9. C++ 最优解

cpp 复制代码
class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        vector<int> buy(k + 1, -1000000000);
        vector<int> sell(k + 1, 0);

        for (int price : prices) {
            for (int t = k; t >= 1; --t) {
                sell[t] = max(sell[t], buy[t] + price);
                buy[t] = max(buy[t], sell[t - 1] - price);
            }
        }

        return sell[k];
    }
};

代码对应关系

cpp 复制代码
sell[t] = max(sell[t], buy[t] + price);

表示:

text 复制代码
今天不卖
vs
今天进行第 t 次卖出
buy[t] = max(buy[t], sell[t - 1] - price);

表示:

text 复制代码
今天不买
vs
前 t-1 笔交易完成以后,今天进行第 t 次买入

交易次数从后往前:

cpp 复制代码
for (int t = k; t >= 1; --t)

主要是为了让 buy[t] 使用的 sell[t-1] 仍然是当前这一轮更新之前保存的状态。


10. Java 最优解

java 复制代码
class Solution {
    public int maxProfit(int k, int[] prices) {
        int[] buy = new int[k + 1];
        int[] sell = new int[k + 1];

        for (int t = 1; t <= k; t++) {
            buy[t] = -1000000000;
        }

        for (int price : prices) {
            for (int t = k; t >= 1; t--) {
                sell[t] = Math.max(sell[t], buy[t] + price);
                buy[t] = Math.max(buy[t], sell[t - 1] - price);
            }
        }

        return sell[k];
    }
}

11. Java 语法解释

11.1 创建一维数组

java 复制代码
int[] buy = new int[k + 1];

表示创建一个长度为 k+1 的整数数组,对应 C++:

cpp 复制代码
vector<int> buy(k + 1);

Java 的 int 数组默认所有元素都是:

text 复制代码
0

11.2 增强 for 循环

java 复制代码
for (int price : prices)

表示依次取出 prices 中的每个元素,相当于 C++:

cpp 复制代码
for (int price : prices)

也就是:

text 复制代码
第一天价格
第二天价格
第三天价格
...

11.3 Math.max

Java:

java 复制代码
Math.max(a, b)

对应 C++:

cpp 复制代码
max(a, b)

所以:

java 复制代码
sell[t] = Math.max(sell[t], buy[t] + price);

就是:

text 复制代码
以前的最大利润
和
今天卖出的利润
取更大的一个

11.4 t--

java 复制代码
for (int t = k; t >= 1; t--)

表示:

text 复制代码
k, k-1, k-2, ..., 1

和 C++ 的写法基本完全一样。


12. Python 最优解

python 复制代码
class Solution:
    def maxProfit(self, k, prices):
        buy = [-10**9] * (k + 1)
        sell = [0] * (k + 1)

        for price in prices:
            for t in range(k, 0, -1):
                sell[t] = max(sell[t], buy[t] + price)
                buy[t] = max(buy[t], sell[t - 1] - price)

        return sell[k]

13. Python 语法解释

13.1 创建数组

python 复制代码
sell = [0] * (k + 1)

如果:

text 复制代码
k = 3

得到:

python 复制代码
[0, 0, 0, 0]

同理:

python 复制代码
buy = [-10**9] * (k + 1)

表示创建 k+1 个非常小的初始值。

13.2 10**9

Python:

python 复制代码
10**9

表示:

text 复制代码
10 的 9 次方
= 1000000000

所以:

python 复制代码
-10**9

就是:

text 复制代码
-1000000000

13.3 遍历数组

python 复制代码
for price in prices:

表示每次取出一个股票价格,相当于 C++:

cpp 复制代码
for (int price : prices)

13.4 倒序遍历

python 复制代码
for t in range(k, 0, -1):

假设:

text 复制代码
k = 4

那么 t 依次为:

text 复制代码
4, 3, 2, 1

其中:

text 复制代码
range(开始值, 结束值, 步长)

所以:

python 复制代码
range(k, 0, -1)

表示从 k 开始,每次减 1,直到 1

13.5 Python 的 max

python 复制代码
max(a, b)

直接返回两者中较大的一个,所以:

python 复制代码
buy[t] = max(buy[t], sell[t - 1] - price)

就是:

text 复制代码
以前的第 t 次买入状态
vs
今天进行第 t 次买入后的状态

14. 复杂度

最优动态规划中:

text 复制代码
每天遍历一次:n
每一天遍历 k 个交易状态:k

所以:

text 复制代码
时间复杂度:O(n × k)
空间复杂度:O(k)

相比直观 DP 的:

text 复制代码
O(k × n²)

省掉了对所有历史买入日的重复枚举。


15. 核心记忆

状态

text 复制代码
buy[t]  = 第 t 次买入后的最好状态
sell[t] = 第 t 次卖出后的最大利润

买入

text 复制代码
buy[t] = max(
    以前的 buy[t],
    sell[t-1] - 今天价格
)

卖出

text 复制代码
sell[t] = max(
    以前的 sell[t],
    buy[t] + 今天价格
)

一句话理解

text 复制代码
sell[t-1]
    ↓ 减去股价
buy[t]
    ↓ 加上股价
sell[t]

每个 max() 永远只是在比较:

text 复制代码
以前的最好方案
vs
今天操作产生的新方案

题目是最多交易 k 次,不是必须交易 k 次;DP 也不会提前固定哪天买卖,而是在每一天持续保留当前能达到的最佳状态。

相关推荐
a187927218311 小时前
【算法】树(二):队列与祖先——BFS 骨架三配件、短路透传与合流
算法·leetcode·二叉树··bfs·算法讲解·树遍历
syagain_zsx1 小时前
算法基础篇 · 03 枚举(C++ 题解)
c++·算法·二进制·枚举
weixin_307779132 小时前
C++代码实现MATLAB中的ode23tb函数功能
开发语言·c++·算法·matlab
我是章汕呐3 小时前
地级市能源消耗量及消耗强度数据【2006-2023年】平衡面板
人工智能·经验分享·算法·回归
charliejohn3 小时前
计算机考研 408 数据结构 堆的插入与删除 堆排序
算法
老鱼说AI3 小时前
从点积到希尔伯特空间:向量内积的几何本质与大模型相似度度量
人工智能·深度学习·线性代数·算法·机器学习·数学建模
地平线开发者4 小时前
模型部署|如何解决算子约束
深度学习·算法·自动驾驶
Navigator_Z5 小时前
LeetCode //C - 1254. Number of Closed Islands
c语言·算法·leetcode
deepseek235 小时前
GPT-6 Astra 破解 FrontierMath 九年悬案拆解:调和熵投票规则反证核恒非空,局部搜索如何终结反例悬赏
人工智能·算法·ai agent