动态规划—188. 买卖股票的最佳时机 IV
- 题目描述
- 前言
- 基本思路
- 1. 问题定义
- 交易规则:
- 2. 理解问题和递推关系
- 两种情况:
- 状态定义:
- 状态转移方程:
- 初始条件:
- 3. 解决方法
- 动态规划方法
- 特殊情况:当 `k` 大于等于 `prices` 长度的一半时
- 伪代码:
- 4. 进一步优化
- 5. 小总结
- Python代码
- Python代码解释总结:
- C++代码
- C++代码解释总结:
- 最终总结
题目描述
前言
买卖股票的最佳时机 IV 是股票交易系列问题中的一个变种,难度较高。该问题要求我们最多进行 k
次交易,每次交易包含一次买入和一次卖出,目标是通过这 k
次交易获取最大利润。该问题结合了 动态规划 和 贪心 的思想,要求我们合理规划每次买卖的时机以获取最大利润。
基本思路
1. 问题定义
给定一个整数 k
,表示最多可以进行的交易次数,以及一个整数数组 prices
,其中 prices[i]
表示股票第 i
天的价格,目标是计算最多进行 k
次交易可以获得的最大利润。
交易规则:
- 每次交易必须包含买入和卖出。
- 同一天不能同时进行买入和卖出。
- 你可以在一天内完成一次交易,即买入和卖出可以在同一天发生。
2. 理解问题和递推关系
两种情况:
- 如果
k
大于等于prices
的一半,那么我们可以进行无限次交易,问题退化为类似 无限次交易 的问题,可以用贪心策略来解决。 - 如果
k
小于prices
的一半,那么我们需要用动态规划来解决问题,控制交易次数。
状态定义:
我们可以定义 dp[i][j][0]
和 dp[i][j][1]
来表示在第 i
天结束时,最多进行了 j
次交易的两种状态:
dp[i][j][0]
表示第i
天结束时,没有持有股票,最多进行了j
次交易的最大利润。dp[i][j][1]
表示第i
天结束时,持有股票,最多进行了j
次交易的最大利润。
状态转移方程:
-
不持有股票的状态:
dp[i][j][0]
:要么我们什么都不做,保持昨天不持有股票的状态dp[i-1][j][0]
,要么今天卖出了股票,利润为dp[i-1][j][1] + prices[i]
:
d p [ i ] [ j ] [ 0 ] = max ( d p [ i − 1 ] [ j ] [ 0 ] , d p [ i − 1 ] [ j ] [ 1 ] + p r i c e s [ i ] ) dp[i][j][0] = \max(dp[i-1][j][0], dp[i-1][j][1] + prices[i]) dp[i][j][0]=max(dp[i−1][j][0],dp[i−1][j][1]+prices[i])
-
持有股票的状态:
dp[i][j][1]
:要么我们什么都不做,保持昨天持有股票的状态dp[i-1][j][1]
,要么今天买入了股票,利润为dp[i-1][j-1][0] - prices[i]
:
d p [ i ] [ j ] [ 1 ] = max ( d p [ i − 1 ] [ j ] [ 1 ] , d p [ i − 1 ] [ j − 1 ] [ 0 ] − p r i c e s [ i ] ) dp[i][j][1] = \max(dp[i-1][j][1], dp[i-1][j-1][0] - prices[i]) dp[i][j][1]=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 天结束时,如果持有股票,则利润为-prices[0]
。
3. 解决方法
动态规划方法
- 初始化
dp
数组,表示每天结束时,最多进行j
次交易的最大利润。 - 根据状态转移方程,逐步更新每一天的状态。
- 最终结果为
dp[n-1][k][0]
,表示在第n-1
天结束时,不持有股票且最多进行了k
次交易的最大利润。
特殊情况:当 k
大于等于 prices
长度的一半时
如果 k >= len(prices) // 2
,那么问题退化为可以进行无限次交易的情况,类似于买卖股票 II 问题,只需要贪心算法即可解决。
伪代码:
if k >= len(prices) // 2:
return solve_unlimited_transactions()
initialize dp array with dimensions (n, k+1, 2)
for each day i from 1 to n-1:
for each transaction j from 1 to k:
update dp[i][j][0] and dp[i][j][1] using state transition formulas
return dp[n-1][k][0]
4. 进一步优化
- 空间优化:可以通过滚动数组,将
dp[i][j][0]
和dp[i][j][1]
仅用两层数组存储,降低空间复杂度。
5. 小总结
- 问题思路:通过动态规划,定义持有股票和不持有股票的状态,并根据交易次数进行状态转移。
- 时间复杂度:时间复杂度为
O(n*k)
,适合处理中等规模的n
和k
。
以上就是买卖股票的最佳时机 IV问题的基本思路。
Python代码
class Solution:
def maxProfit(self, k: int, prices: list[int]) -> int:
if not prices:
return 0
n = len(prices)
# 当 k 大于等于 n/2 时,相当于可以进行无限次交易
if k >= n // 2:
max_profit = 0
for i in range(1, n):
if prices[i] > prices[i - 1]:
max_profit += prices[i] - prices[i - 1]
return max_profit
# 初始化 dp 数组
dp = [[[0, 0] for _ in range(k + 1)] for _ in range(n)]
# 初始化第一天的状态
for j in range(1, k + 1):
dp[0][j][1] = -prices[0]
# 动态规划状态转移
for i in range(1, n):
for j in range(1, k + 1):
dp[i][j][0] = max(dp[i - 1][j][0], dp[i - 1][j][1] + prices[i])
dp[i][j][1] = max(dp[i - 1][j][1], dp[i - 1][j - 1][0] - prices[i])
# 返回结果
return dp[n - 1][k][0]
Python代码解释总结:
- 特殊情况处理:当
k >= len(prices) // 2
时,问题转化为无限次交易,通过贪心策略解决。 - 初始化和状态转移:通过动态规划状态转移公式,更新
dp[i][j][0]
和dp[i][j][1]
。 - 返回结果:最终返回
dp[n-1][k][0]
,即不持有股票且进行了k
次交易的最大利润。
C++代码
class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
int n = prices.size();
if (n == 0) return 0;
// 当 k 大于等于 n/2 时,相当于可以进行无限次交易
if (k >= n / 2) {
int max_profit = 0;
for (int i = 1; i < n; ++i) {
if (prices[i] > prices[i - 1]) {
max_profit += prices[i] - prices[i - 1];
}
}
return max_profit;
}
// 初始化 dp 数组
vector<vector<vector<int>>> dp(n, vector<vector<int>>(k + 1, vector<int>(2, 0)));
// 初始化第一天的状态
for (int j = 1; j <= k; ++j) {
dp[0][j][1] = -prices[0];
}
// 动态规划状态转移
for (int i = 1; i < n; ++i) {
for (int j = 1; j <= k; ++j) {
dp[i][j][0] = max(dp[i - 1][j][0], dp[i - 1][j][1] + prices[i]);
dp[i][j][1] = max(dp[i - 1][j][1], dp[i - 1][j - 1][0] - prices[i]);
}
}
// 返回结果
return dp[n - 1][k][0];
}
};
C++代码解释总结:
- 处理无限次交易的情况:当
k >= n // 2
时,问题退化为可以进行无限次交易,用贪心算法解决。 - 状态转移:通过动态规划更新
dp[i][j][0]
和dp[i][j][1]
。 - 返回结果:返回
dp[n-1][k][0]
,即最大利润。
最终总结
- 核心思想:通过动态规划,分为持有股票和不持有股票两种状态,并根据交易次数进行状态转移。通过
dp[i][j][0]
和dp[i][j][1]
来跟踪每一天、每一次交易的利润状态。 - 时间复杂度:时间复杂度为
O(n*k)
,空间复杂度为O(n*k)
,可以通过滚动数组优化空间复杂度。