股票交易问题解题与动态规划实践 | 豆包MarsCode AI刷题

134 阅读4分钟

股票交易问题解题与动态规划实践

题目背景与分析

这次我们讨论的是一个股票交易的经典问题,规则如下:

  1. 每次交易之前必须先完成一次卖出操作,不能连续买入。
  2. 每次卖出后,需要等待一天的冷冻期,冷冻期内不能买入。
  3. 我们的目标是通过设计合理的交易策略,获得最大利润。

这个问题的关键在于如何在冷冻期的限制下,优化买入和卖出时机。直接的暴力求解可能会因为复杂的状态组合导致性能低下,而动态规划则是解决这一问题的高效方法。


解题思路:动态规划

在动态规划中,我们需要定义每一天的状态,并通过状态转移方程描述状态的变化。以下是解题的关键思路:

  1. 状态定义

    • 持有状态(hold) :当天手里持有股票的最大利润。
    • 冷冻状态(freeze) :当天手里没有股票,但因为前一天刚卖出而进入冷冻期的最大利润。
    • 非冷冻状态(rest) :当天手里没有股票,也不处于冷冻期的最大利润。
  2. 状态转移方程

    • 持有状态(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])

  3. 初始状态

    • 第一天买入股票:hold[0] = -stocks[0]
    • 第一天没有操作:freeze[0] = rest[0] = 0
  4. 最终结果

    • 最后的最大利润为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);
    }
}

代码详解

  1. 状态变量初始化

    • hold 初始化为 -stocks[0] 表示第一天买入股票,计算利润时需要减去买入成本。
    • freezerest 初始化为 0,因为第一天无法卖出股票。
  2. 状态转移

    • 通过循环遍历每一天的价格,根据状态转移方程更新 holdfreezerest
  3. 结果计算

    • 返回最后一天 freezerest 的较大值,因为最终手里不持有股票时才能实现利润最大化。

复杂度分析

  • 时间复杂度:遍历整个数组,时间复杂度为 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
  • 输出:3


个人实践与反思

这道题是典型的动态规划问题,在实现过程中需要注意以下几点:

  1. 状态定义清晰:动态规划的核心在于正确定义状态和状态转移方程。
  2. 边界条件处理:对于只有一天或价格单调的情况,要做好初始化处理。
  3. 优化思路:通过状态压缩减少空间复杂度,提升算法性能。
扩展思考

如果股票的买卖次数有限(如最多两次交易),我们可以增加一个交易次数的状态维度。例如定义 dp[k][i] 表示最多进行 k 次交易后,第 i 天的最大利润,从而进一步解决更复杂的股票问题。

通过这道题的实践,我们可以掌握动态规划的实际应用场景和性能优化技巧,同时也为解决类似的金融数据问题打下了基础。