股票交易问题解题与动态规划实践
题目背景与分析
这次我们讨论的是一个股票交易的经典问题,规则如下:
- 每次交易之前必须先完成一次卖出操作,不能连续买入。
- 每次卖出后,需要等待一天的冷冻期,冷冻期内不能买入。
- 我们的目标是通过设计合理的交易策略,获得最大利润。
这个问题的关键在于如何在冷冻期的限制下,优化买入和卖出时机。直接的暴力求解可能会因为复杂的状态组合导致性能低下,而动态规划则是解决这一问题的高效方法。
解题思路:动态规划
在动态规划中,我们需要定义每一天的状态,并通过状态转移方程描述状态的变化。以下是解题的关键思路:
-
状态定义:
- 持有状态(hold) :当天手里持有股票的最大利润。
- 冷冻状态(freeze) :当天手里没有股票,但因为前一天刚卖出而进入冷冻期的最大利润。
- 非冷冻状态(rest) :当天手里没有股票,也不处于冷冻期的最大利润。
-
状态转移方程:
-
持有状态(hold) : 持有股票可能来源于两种情况:
-
前一天已经持有股票。
-
前一天处于非冷冻状态,今天买入股票。
hold[i]=max(hold[i−1],rest[i−1]−stocks[i])hold[i] = \max(hold[i-1], rest[i-1] - stocks[i])
-
-
冷冻状态(freeze) : 冷冻状态只能来源于卖出股票:
freeze[i]=hold[i−1]+stocks[i]freeze[i] = hold[i-1] + stocks[i]
-
非冷冻状态(rest) : 非冷冻状态可能来源于两种情况:
-
昨天也是非冷冻状态。
-
昨天是冷冻状态。
rest[i]=max(rest[i−1],freeze[i−1])rest[i] = \max(rest[i-1], freeze[i-1])
-
-
-
初始状态:
- 第一天买入股票:
hold[0] = -stocks[0]。 - 第一天没有操作:
freeze[0] = rest[0] = 0。
- 第一天买入股票:
-
最终结果:
- 最后的最大利润为
rest[n-1]和freeze[n-1]中的较大值,因为手里持有股票时不可能实现最大利润。
- 最后的最大利润为
代码实现
以下是基于动态规划的代码实现:
public class Main {
public static int solution(int[] stocks) {
if (stocks == null || stocks.length == 0) return 0;
int n = stocks.length;
// 初始化状态变量
int hold = -stocks[0]; // 第一天买入股票
int freeze = 0; // 冷冻期利润
int rest = 0; // 非冷冻期利润
for (int i = 1; i < n; i++) {
int prevHold = hold;
int prevFreeze = freeze;
int prevRest = rest;
// 更新状态
hold = Math.max(prevHold, prevRest - stocks[i]);
freeze = prevHold + stocks[i];
rest = Math.max(prevRest, prevFreeze);
}
// 最终利润
return Math.max(freeze, rest);
}
public static void main(String[] args) {
// 测试用例
System.out.println(solution(new int[]{1, 2}) == 1);
System.out.println(solution(new int[]{2, 1}) == 0);
System.out.println(solution(new int[]{1, 2, 3, 0, 2}) == 3);
System.out.println(solution(new int[]{2, 3, 4, 5, 6, 7}) == 5);
System.out.println(solution(new int[]{1, 6, 2, 7, 13, 2, 8}) == 12);
}
}
代码详解
-
状态变量初始化:
hold初始化为-stocks[0]表示第一天买入股票,计算利润时需要减去买入成本。freeze和rest初始化为 0,因为第一天无法卖出股票。
-
状态转移:
- 通过循环遍历每一天的价格,根据状态转移方程更新
hold、freeze和rest。
- 通过循环遍历每一天的价格,根据状态转移方程更新
-
结果计算:
- 返回最后一天
freeze和rest的较大值,因为最终手里不持有股票时才能实现利润最大化。
- 返回最后一天
复杂度分析
- 时间复杂度:遍历整个数组,时间复杂度为 O(n)O(n)。
- 空间复杂度:只使用了常数个变量,空间复杂度为 O(1)O(1)。
测试用例分析
用例1:
- 输入:
stocks = [1, 2] - 分析:第 1 天买入,第 2 天卖出,利润为
2 - 1 = 1。 - 输出:
1
用例2:
- 输入:
stocks = [2, 1] - 分析:无盈利机会,利润为
0。 - 输出:
0
用例3:
-
输入:
stocks = [1, 2, 3, 0, 2] -
分析:
- 第 1 天买入,第 3 天卖出,利润为
3 - 1 = 2。 - 冷冻期后,第 5 天买入并卖出,利润为
2 - 0 = 1。 - 总利润为
2 + 1 = 3。
- 第 1 天买入,第 3 天卖出,利润为
-
输出:
3
个人实践与反思
这道题是典型的动态规划问题,在实现过程中需要注意以下几点:
- 状态定义清晰:动态规划的核心在于正确定义状态和状态转移方程。
- 边界条件处理:对于只有一天或价格单调的情况,要做好初始化处理。
- 优化思路:通过状态压缩减少空间复杂度,提升算法性能。
扩展思考
如果股票的买卖次数有限(如最多两次交易),我们可以增加一个交易次数的状态维度。例如定义 dp[k][i] 表示最多进行 k 次交易后,第 i 天的最大利润,从而进一步解决更复杂的股票问题。
通过这道题的实践,我们可以掌握动态规划的实际应用场景和性能优化技巧,同时也为解决类似的金融数据问题打下了基础。