|
1 | | -# [188. 买卖股票的最佳时机 IV](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv/description/?envType=study-plan-v2&envId=dynamic-programming) |
| 1 | +# [188. 买卖股票的最佳时机 IV](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv/description) |
2 | 2 |
|
3 | | -> **作者**:弘树 |
4 | | -> **日期**:2024-09-22 |
5 | | -> **所用时间**:11min |
| 3 | +> **日期**:2024-09-22、2026-02-28 |
| 4 | +> **所用时间**:11min |
| 5 | +> **知识点**:动态规划、记忆化搜索 |
6 | 6 |
|
7 | | -## 1. 动态规划 |
| 7 | +## 1. 题目描述 |
8 | 8 |
|
9 | | -### 1.1 状态表示 |
| 9 | +给定一个整数 `k` 和一个整数数组 `prices`,其中 `prices[i]` 表示第 `i` 天的股票价格。要求计算能够获得的最大利润。 |
10 | 10 |
|
11 | | -可以分为2种情况,分别为$f[i][0]$和$f[i][1]$ |
| 11 | +**限制条件:** |
12 | 12 |
|
13 | | -对于$f[i][0]$来说: |
| 13 | +- 最多可以完成 **k 笔** 交易(一笔交易指一次买入 + 一次卖出)。 |
| 14 | +- 不能同时参与多笔交易:再次买入前必须先卖出当前持有的股票。 |
14 | 15 |
|
15 | | -1. $f[i][0][0]$表示在第$i$天进行完操作不持有股票同时购买股票次数为0的最大利润 |
16 | | -2. $f[i][0][1]$表示在第$i$天进行完操作不持有股票同时购买股票次数为1的最大利润 |
17 | | -3. ...... |
18 | | -4. $f[i][0][k]$表示在第$i$天进行完操作不持有股票同时购买股票次数为k的最大利润 |
| 16 | +**示例 1:** |
19 | 17 |
|
20 | | -对于$f[i][1]$来说: |
| 18 | +- **输入**:k = 2, prices = [2,4,1] |
| 19 | +- **输出**:2 |
| 20 | +- **解释**:第 1 天买入(价格 2),第 2 天卖出(价格 4),利润 2。 |
21 | 21 |
|
22 | | -1. $f[i][1][0]$表示在第$i$天进行完操作不持有股票同时购买股票次数为0的最大利润 |
23 | | -2. $f[i][1][1]$表示在第$i$天进行完操作不持有股票同时购买股票次数为1的最大利润 |
24 | | -3. ...... |
25 | | -4. $f[i][1][k - 1]$表示在第$i$天进行完操作持有股票同时购买股票次数为$k - 1$的最大利润 |
| 22 | +**示例 2:** |
26 | 23 |
|
27 | | -### 1.2 状态计算 |
| 24 | +- **输入**:k = 2, prices = [3,2,6,5,0,3] |
| 25 | +- **输出**:7 |
| 26 | +- **解释**:第 2 天买入(价格 2),第 3 天卖出(价格 6),利润 4;第 5 天买入(价格 0),第 6 天卖出(价格 3),利润 3。总利润 4 + 3 = 7。 |
28 | 27 |
|
29 | | -#### 1.2.1 $f[i][0]$状态计算 |
| 28 | +**约束:** |
30 | 29 |
|
31 | | -若第$i$不持有股票,则有2种可能: |
| 30 | +- $1 \le k \le 100$ |
| 31 | +- $1 \le \text{prices.length} \le 1000$ |
| 32 | +- $0 \le \text{prices}[i] \le 1000$ |
32 | 33 |
|
33 | | - - 第$i - 1$就没有股票,则 |
| 34 | +--- |
34 | 35 |
|
35 | | - $$f[i][0][0] = f[i - 1][0][0]$$ |
| 36 | +## 2. 「动态规划 / 记忆化搜索」 |
36 | 37 |
|
37 | | - $$f[i][0][1] = f[i - 1][0][1]$$ |
| 38 | +**思路:** |
38 | 39 |
|
39 | | - $$……$$ |
40 | | - |
41 | | - $$f[i][0][k] = f[i - 1][0][k]$$ |
| 40 | +与「123. 买卖股票的最佳时机 III」相同,将**最多 2 笔**推广为**最多 k 笔**。用**记忆化搜索**:`dfs(i, j, hold)` 表示考虑前 `i+1` 天、**剩余**可完成 `j` 笔交易、当前是否持有股票时的最大利润。 |
42 | 41 |
|
43 | | - - 第$i - 1$持有股票,在第$i$天出售,则 |
44 | | - |
45 | | - $$f[i][0][0] = f[i - 1][1][0] + prices[i]$$ |
| 42 | +- 若 `j == 0`:不能再交易,返回 0。 |
| 43 | +- 若 `i < 0`:没有天数,持有则非法返回 $-\infty$,否则返回 0。 |
| 44 | +- 若当前持有:今天可**不卖**(`dfs(i-1, j, True)`)或**卖出**(`dfs(i-1, j-1, False) + prices[i]`,用掉 1 笔交易)。 |
| 45 | +- 若当前不持有:今天可**不买**(`dfs(i-1, j, False)`)或**买入**(`dfs(i-1, j, True) - prices[i]`)。 |
46 | 46 |
|
47 | | - $$f[i][0][1] = f[i - 1][1][1] + prices[i]$$ |
| 47 | +答案为 `dfs(len(prices)-1, k, False)`,即从最后一天、剩余 k 笔、不持有出发的最大利润。 |
48 | 48 |
|
49 | | - $$……$$ |
| 49 | +**复杂度分析:** |
50 | 50 |
|
51 | | - $$f[i][0][k - 1] = f[i - 1][1][k - 1] + prices[i]$$ |
| 51 | +- 时间复杂度:$O(n \cdot k)$(状态 $(i, j, \text{hold})$ 共 $O(n \cdot k \cdot 2)$,每状态 $O(1)$ 转移) |
| 52 | +- 空间复杂度:$O(n \cdot k)$(递归栈与 cache) |
52 | 53 |
|
| 54 | +**Python3** |
53 | 55 |
|
54 | | -综上,当$j \in [0, k - 1]$时,$f[i][0][j]$的状态转移方程为: |
55 | | - |
56 | | -$$ |
57 | | - f[i][0][j] = max(f[i - 1][0][j], f[i - 1][1][j] + prices[i]) |
58 | | -$$ |
59 | | - |
60 | | -当$j = k$时,$f[i][0][j]$的状态转移方程为: |
61 | | - |
62 | | -$$ |
63 | | - f[i][0][k] = f[i - 1][0][k] |
64 | | -$$ |
65 | | - |
66 | | -#### 1.2.2 $f[i][1]$状态计算 |
67 | | - |
68 | | -若第$i$持有股票,则有2种可能: |
69 | | - |
70 | | - - 第$i - 1$就持有股票,则 |
71 | | - |
72 | | - $$f[i][1][0] = f[i - 1][1][0]$$ |
73 | | - |
74 | | - $$f[i][1][1] = f[i - 1][1][1]$$ |
75 | | - |
76 | | - $$……$$ |
77 | | - |
78 | | - $$f[i][1][k - 1] = f[i - 1][1][k - 1]$$ |
79 | | - |
80 | | - - 第$i - 1$不持有股票,在第$i$天购买,则 |
81 | | - |
82 | | - $$f[i][1][0] = f[i - 1][0][1] - prices[i]$$ |
83 | | - |
84 | | - $$f[i][1][1] = f[i - 1][0][2] - prices[i]$$ |
85 | | - |
86 | | - $$……$$ |
87 | | - |
88 | | - $$f[i][1][k - 1] = f[i - 1][0][k] - prices[i]$$ |
89 | | - |
90 | | -对于$f[i][1]$的状态转移方程如下: |
91 | | - |
92 | | -$$ |
93 | | - f[i][1][j] = max(f[i - 1][1][j], f[i - 1][0][j + 1] - prices[i]), j \in [0, k - 1] |
94 | | -$$ |
95 | | - |
96 | | -最后答案为$\max(f[n][j]), j\in [0, k]$。 |
97 | | - |
98 | | -- 时间复杂度:$O(nk)$ |
99 | | -- 空间复杂度:$O(2nk)$ |
100 | | - |
101 | | -```C++ |
102 | | -class Solution { |
103 | | -public: |
104 | | - long long f[1010][2][110]; |
105 | | - |
106 | | - int maxProfit(int k, vector<int>& prices) { |
107 | | - for (int i = 0; i < k; i ++) f[1][0][i] = INT_MIN; |
108 | | - for (int i = 0; i < k - 1; i ++) f[1][1][i] = INT_MIN; |
109 | | - f[1][0][k] = 0, f[1][1][k - 1] = -prices[0]; |
110 | | - for (int i = 2; i <= prices.size(); i ++) |
111 | | - { |
112 | | - for (int j = 0; j < k; j ++) |
113 | | - f[i][0][j] = max(f[i - 1][0][j], f[i - 1][1][j] + prices[i - 1]); |
114 | | - f[i][0][k] = f[i - 1][0][k]; |
115 | | - for (int j = 0; j < k; j ++) |
116 | | - f[i][1][j] = max(f[i - 1][1][j], f[i - 1][0][j + 1] - prices[i - 1]); |
117 | | - } |
118 | | - long long ans = 0; |
119 | | - for (int i = 0; i <= k; i ++) ans = max(ans, f[prices.size()][0][i]); |
120 | | - return ans; |
121 | | - } |
122 | | -}; |
123 | | -``` |
124 | | - |
125 | | -## 2. 滚动数组空间优化 |
126 | | - |
127 | | -可以发现第$i$个时刻的状态总依赖于第$i - 1$时刻的状态,所以可以使用$2k$个常量保存上一个时刻的状态,这里不再演示 |
| 56 | +```python |
| 57 | +class Solution: |
| 58 | + def maxProfit(self, k: int, prices: List[int]) -> int: |
| 59 | + @cache |
| 60 | + def dfs(i, j, hold): |
| 61 | + if j == 0: |
| 62 | + return 0 |
| 63 | + if i < 0: |
| 64 | + return -inf if hold else 0 |
| 65 | + if hold: |
| 66 | + return max(dfs(i - 1, j, hold), dfs(i - 1, j - 1, False) - prices[i]) |
| 67 | + return max(dfs(i - 1, j, hold), dfs(i - 1, j, True) + prices[i]) |
| 68 | + return dfs(len(prices) - 1, k, False) |
| 69 | +``` |
0 commit comments