LeetCode 123:买卖股票的最佳时机 III

问题核心与思路拆解
需计算最多两次交易 的最大利润,核心是通过 四个状态 建模交易过程:
buy1
:仅完成第一次买入(未卖出);sell1
:完成第一次买卖(一笔交易);buy2
:完成一笔交易后,第二次买入(未卖出);sell2
:完成两次买卖(两笔交易)。
通过状态转移方程 逐天更新这四个状态,最终 sell2
即为最大利润(覆盖"0、1、2次交易"的最优解)。
算法步骤详解
1. 状态定义
状态 | 含义 | 初始值(第0天) |
---|---|---|
buy1 |
第一次买入后未卖出 | -prices[0] (买入第0天股票) |
sell1 |
第一次卖出(完成一笔交易) | 0 (第0天买入并卖出,利润0) |
buy2 |
完成一笔交易后,第二次买入未卖出 | -prices[0] (第0天完成一次交易后再买入) |
sell2 |
完成两次卖出(完成两笔交易) | 0 (第0天完成两次交易,利润0) |
2. 状态转移方程
遍历每天价格 price
,更新四个状态(依赖前一天的旧状态):
buy1
:可保持现状,或在"未交易"状态下买入当前股票:
buy1=max(旧buy1, −price) \text{buy1} = \max(\text{旧buy1},\ -price) buy1=max(旧buy1, −price)sell1
:可保持现状,或在"第一次买入"状态下卖出当前股票:
sell1=max(旧sell1, 旧buy1+price) \text{sell1} = \max(\text{旧sell1},\ \text{旧buy1} + price) sell1=max(旧sell1, 旧buy1+price)buy2
:可保持现状,或在"第一次卖出"状态下买入当前股票:
buy2=max(旧buy2, 旧sell1−price) \text{buy2} = \max(\text{旧buy2},\ \text{旧sell1} - price) buy2=max(旧buy2, 旧sell1−price)sell2
:可保持现状,或在"第二次买入"状态下卖出当前股票:
sell2=max(旧sell2, 旧buy2+price) \text{sell2} = \max(\text{旧sell2},\ \text{旧buy2} + price) sell2=max(旧sell2, 旧buy2+price)
3. 边界与遍历
- 特殊情况 :若数组长度
< 2
,直接返回0
(无法交易)。 - 遍历过程 :从第1天开始,逐天更新四个状态,确保每次转移使用前一天的旧值(避免状态污染)。
完整代码(Java)
java
class Solution {
public int maxProfit(int[] prices) {
// 特殊情况:无法交易
if (prices == null || prices.length < 2) {
return 0;
}
int n = prices.length;
// 初始化四个状态(第0天)
int buy1 = -prices[0]; // 第一次买入
int sell1 = 0; // 第一次卖出(买入后立即卖出,利润0)
int buy2 = -prices[0]; // 第二次买入(第一次卖出后立即买入)
int sell2 = 0; // 第二次卖出(两次交易都在第0天,利润0)
// 逐天更新状态(从第1天开始)
for (int i = 1; i < n; i++) {
int price = prices[i];
// 计算新状态(基于前一天的旧状态)
int newBuy1 = Math.max(buy1, -price); // 保持或新买入(未交易→买入)
int newSell1 = Math.max(sell1, buy1 + price); // 保持或卖出(第一次买入→卖出)
int newBuy2 = Math.max(buy2, sell1 - price); // 保持或新买入(第一次卖出→买入)
int newSell2 = Math.max(sell2, buy2 + price); // 保持或卖出(第二次买入→卖出)
// 更新状态(覆盖旧值,供下一轮使用)
buy1 = newBuy1;
sell1 = newSell1;
buy2 = newBuy2;
sell2 = newSell2;
}
// sell2 覆盖了0、1、2次交易的最优解
return sell2;
}
}
代码逻辑验证(示例1:prices = [3,3,5,0,0,3,1,4]
)
天数 i |
价格 price |
buy1 变化 |
sell1 变化 |
buy2 变化 |
sell2 变化 |
---|---|---|---|---|---|
0 | 3 | -3 |
0 |
-3 |
0 |
1 | 3 | max(-3, -3)=-3 |
max(0, -3+3=0)=0 |
max(-3, 0-3=-3)=-3 |
max(0, -3+3=0)=0 |
2 | 5 | max(-3, -5)=-3 |
max(0, -3+5=2)=2 |
max(-3, 2-5=-3)=-3 |
max(0, -3+5=2)=2 |
3 | 0 | max(-3, -0=0)=0 |
max(2, 0+0=0)=2 |
max(-3, 2-0=2)=2 |
max(2, 2+0=2)=2 |
4 | 0 | max(0, -0=0)=0 |
max(2, 0+0=0)=2 |
max(2, 2-0=2)=2 |
max(2, 2+0=2)=2 |
5 | 3 | max(0, -3)=0 |
max(2, 0+3=3)=3 |
max(2, 3-3=0)=2 |
max(2, 2+3=5)=5 |
6 | 1 | max(0, -1)=0 |
max(3, 0+1=1)=3 |
max(2, 3-1=2)=2 |
max(5, 2+1=3)=5 |
7 | 4 | max(0, -4)=0 |
max(3, 0+4=4)=4 |
max(2, 4-4=0)=2 |
max(5, 2+4=6)=6 |
最终 sell2=6
,与示例1结果一致。
复杂度分析
- 时间复杂度 :
O(n)
(遍历数组一次,每次更新4个状态,O(1)
操作)。 - 空间复杂度 :
O(1)
(仅用4个变量存储状态,无额外空间)。
该方法通过 状态压缩+动态规划 ,精准建模"两次交易"的所有可能状态,确保每一步转移高效且无冗余。核心是对"买入/卖出"操作的状态拆分,以及利用滚动更新避免空间浪费,是交易类动态规划问题的经典解法。