动态规划—123. 买卖股票的最佳时机 III
- 题目描述
- 前言
- 基本思路
- 1. 问题定义
- 2. 理解问题和递推关系
- 状态定义:
- 状态转移公式:
- 初始条件:
- 3. 解决方法
- 动态规划方法
- 伪代码:
- 4. 进一步优化
- 5. 小总结
- Python代码
- Python代码解释
- C++代码
- C++代码解释
- 总结
题目描述
前言
买卖股票的最佳时机 III 是一个动态规划问题的经典变种。给定一个数组 prices
,其中 prices[i]
代表第 i
天的股票价格。你最多可以进行 两次交易(一次交易包含一次买入和一次卖出),目标是通过这两次交易获得最大的利润。该问题的难点在于如何合理规划买卖时机,以获取最大收益。
基本思路
1. 问题定义
给定一个数组 prices
,其中 prices[i]
表示股票第 i
天的价格。你最多可以进行两次交易,求这两次交易所能获取的最大利润。注意:两次交易不能交叉进行(必须卖掉一次股票才能再次买入)。
2. 理解问题和递推关系
为了解决该问题,我们可以将其拆解为两个阶段:
- 第一次交易:在某天买入并在稍后的一天卖出,获取第一次交易的最大利润。
- 第二次交易:在某天(第一次交易之后)再次买入股票并卖出,获取第二次交易的最大利润。
状态定义:
我们可以定义 4 个状态:
first_buy[i]
:表示在第i
天结束时,我们第一次买入股票时的最大收益。first_sell[i]
:表示在第i
天结束时,我们第一次卖出股票时的最大收益。second_buy[i]
:表示在第i
天结束时,我们第二次买入股票时的最大收益。second_sell[i]
:表示在第i
天结束时,我们第二次卖出股票时的最大收益。
状态转移公式:
-
第一次买入的状态转移:
f i r s t _ b u y [ i ] = max ( f i r s t _ b u y [ i − 1 ] , − p r i c e s [ i ] ) first\_buy[i] = \max(first\_buy[i-1], -prices[i]) first_buy[i]=max(first_buy[i−1],−prices[i])- 要么我们保持之前的状态不变,继续持有第一次买入的股票;
- 要么在第
i
天以当前价格prices[i]
买入股票。
-
第一次卖出的状态转移:
f i r s t _ s e l l [ i ] = max ( f i r s t _ s e l l [ i − 1 ] , f i r s t _ b u y [ i − 1 ] + p r i c e s [ i ] ) first\_sell[i] = \max(first\_sell[i-1], first\_buy[i-1] + prices[i]) first_sell[i]=max(first_sell[i−1],first_buy[i−1]+prices[i])- 要么我们保持之前的状态不变,继续持有卖出后的最大利润;
- 要么在第
i
天卖出股票,利润为first_buy[i-1] + prices[i]
。
-
第二次买入的状态转移:
s e c o n d _ b u y [ i ] = max ( s e c o n d _ b u y [ i − 1 ] , f i r s t _ s e l l [ i − 1 ] − p r i c e s [ i ] ) second\_buy[i] = \max(second\_buy[i-1], first\_sell[i-1] - prices[i]) second_buy[i]=max(second_buy[i−1],first_sell[i−1]−prices[i])- 要么我们保持之前的状态不变,继续持有第二次买入的股票;
- 要么在第
i
天买入股票,利润为first_sell[i-1] - prices[i]
。
-
第二次卖出的状态转移:
s e c o n d _ s e l l [ i ] = max ( s e c o n d _ s e l l [ i − 1 ] , s e c o n d _ b u y [ i − 1 ] + p r i c e s [ i ] ) second\_sell[i] = \max(second\_sell[i-1], second\_buy[i-1] + prices[i]) second_sell[i]=max(second_sell[i−1],second_buy[i−1]+prices[i])- 要么我们保持之前的状态不变,继续持有第二次卖出的最大利润;
- 要么在第
i
天卖出股票,利润为second_buy[i-1] + prices[i]
。
初始条件:
first_buy[0] = -prices[0]
,第一次买入的初始状态。first_sell[0] = 0
,第一次卖出的初始状态。second_buy[0] = -prices[0]
,第二次买入的初始状态。second_sell[0] = 0
,第二次卖出的初始状态。
3. 解决方法
动态规划方法
- 定义 4 个状态
first_buy[i]
、first_sell[i]
、second_buy[i]
和second_sell[i]
表示在第i
天结束时的最大收益。 - 通过递推公式更新这些状态。
- 最终结果为
second_sell[n-1]
,即最多进行两次交易后在最后一天的最大利润。
伪代码:
initialize first_buy = -prices[0], first_sell = 0, second_buy = -prices[0], second_sell = 0
for i from 1 to n-1:
first_buy = max(first_buy, -prices[i])
first_sell = max(first_sell, first_buy + prices[i])
second_buy = max(second_buy, first_sell - prices[i])
second_sell = max(second_sell, second_buy + prices[i])
return second_sell
4. 进一步优化
- 空间优化:由于每个状态只依赖于前一天的状态,我们可以将动态规划的 4 个状态用常量来代替,优化空间复杂度为
O(1)
。
5. 小总结
- 问题思路:通过定义四个状态,分别对应两次买入和卖出的最大利润,使用动态规划可以有效求解。
- 时间复杂度:该方法的时间复杂度为
O(n)
,空间复杂度可以优化为O(1)
。
以上就是买卖股票的最佳时机 III问题的基本思路。
Python代码
class Solution:
def maxProfit(self, prices: list[int]) -> int:
if not prices:
return 0
# 初始化四个状态
first_buy = second_buy = -prices[0]
first_sell = second_sell = 0
for i in range(1, len(prices)):
# 依次更新四个状态
first_buy = max(first_buy, -prices[i])
first_sell = max(first_sell, first_buy + prices[i])
second_buy = max(second_buy, first_sell - prices[i])
second_sell = max(second_sell, second_buy + prices[i])
# 返回最终状态的最大值
return second_sell
Python代码解释
- 初始化:
first_buy
、second_buy
初始为-prices[0]
,first_sell
和second_sell
初始为0
。 - 状态转移:通过递推公式依次更新四个状态。
- 返回结果:最终返回
second_sell
,即最大利润。
C++代码
#include <vector>
#include <algorithm>
using namespace std;
class Solution {
public:
int maxProfit(vector<int>& prices) {
if (prices.empty()) return 0;
// 初始化四个状态
int first_buy = -prices[0], first_sell = 0;
int second_buy = -prices[0], second_sell = 0;
for (int i = 1; i < prices.size(); ++i) {
// 依次更新四个状态
first_buy = max(first_buy, -prices[i]);
first_sell = max(first_sell, first_buy + prices[i]);
second_buy = max(second_buy, first_sell - prices[i]);
second_sell = max(second_sell, second_buy + prices[i]);
}
// 返回最终状态的最大值
return second_sell;
}
};
C++代码解释
- 初始化:
first_buy
、second_buy
初始为-prices[0]
,first_sell
和second_sell
初始为0
。 - 状态转移:通过递推公式依次更新四个状态。
- 返回结果:最终返回
second_sell
,即最大利润。
总结
- 核心思路:通过动态规划,定义四个状态来跟踪两次买入和两次卖出的最大利润,并通过状态转移公式逐步更新,最终获取最大利润。
- 时间复杂度:时间复杂度为
O(n)
,适合处理大规模数据。 - 空间优化:由于状态之间只依赖前一天的状态,空间复杂度可以优化为
O(1)
,这使得算法在空间效率上达到最优。