股票市场交易策略优化 | 豆包MarsCode AI 刷题

129 阅读6分钟

动态规划问题解读与实现

这道题目属于动态规划中经典的股票买卖问题变种,涉及状态定义、状态转移方程和约束条件。通过分步理解并实现,可以帮助巩固对动态规划问题的掌握。


1. 问题解析

问题要求: 给定股票价格数组 stocks,需要计算在特定交易规则下,能够获得的最大利润:

  1. 可以多次买卖股票,但每次新的买入前必须卖出之前的股票。
  2. 每次卖出后有一天的冷冻期,在冷冻期内不能买入。

示例分析

  • 示例 1

    stocks = [1, 2]
    
    • 第 1 天买入,第二天卖出,利润为 2−1=12 - 1 = 12−1=1。
  • 示例 3

    stocks = [1, 2, 3, 0, 2]
    
    • 第 1 天买入,第 2 天卖出;第 4 天买入,第 5 天卖出。利润为 2−1+2−0=32 - 1 + 2 - 0 = 32−1+2−0=3。
  • 示例 4

    stocks = [2, 3, 4, 5, 6, 7]
    
    • 从第 1 天买入到第 6 天卖出,利润为 7−2=57 - 2 = 57−2=5。

2. 思路分析

  1. 动态规划状态定义

    • 使用三个数组表示不同的状态:

      • hold[i]:第 iii 天结束时,手上持有股票的最大利润。
      • sold[i]:第 iii 天结束时,刚刚卖出股票的最大利润。
      • rest[i]:第 iii 天结束时,处于冷冻期或无操作的最大利润。
  2. 状态转移方程

    ℎ𝑜𝑙𝑑[𝑖]=𝑚𝑎𝑥(ℎ𝑜𝑙𝑑[𝑖−1],𝑟𝑒𝑠𝑡[𝑖−1]−𝑠𝑡𝑜𝑐𝑘𝑠[𝑖])
    
    • 表示要么保持持有状态,要么在冷冻期后买入。
    • 𝑠𝑜𝑙𝑑[𝑖]=ℎ𝑜𝑙𝑑[𝑖−1]+𝑠𝑡𝑜𝑐𝑘𝑠[𝑖]
      
    • 表示当天卖出股票,收益等于前一天持有股票的利润加上当天价格。
    • 𝑟𝑒𝑠𝑡[𝑖]=𝑚𝑎𝑥⁡(𝑟𝑒𝑠𝑡[𝑖−1],𝑠𝑜𝑙𝑑[𝑖−1])
      
    • 表示当天不操作,处于冷冻期或继续保持。
  3. 初始条件

    • 第 0 天:

      • hold[0]=−stocks[0]:只能买入股票。
      • sold[0]=0:不可能卖出股票。
      • rest[0]=0:初始利润为 0。
  4. 目标值

    • 最终的最大利润为: max⁡(sold[n−1],rest[n−1])因为结束时不可能持有股票。
  5. 时间复杂度与空间优化

    • 时间复杂度:O(n)O(n)O(n)。
    • 空间复杂度:可以优化为 O(1)O(1)O(1),通过滚动变量替代数组。

3. 完整代码

public class Main {
    public static int solution(int[] stocks) {
        // 若无股票价格,利润为 0
        if (stocks == null || stocks.length == 0) {
            return 0;
        }
​
        int n = stocks.length;
​
        // 动态规划数组
        int[] hold = new int[n];  // 持有股票
        int[] sold = new int[n];  // 刚卖出股票
        int[] rest = new int[n];  // 休息或冷冻期
​
        // 初始化
        hold[0] = -stocks[0];
        sold[0] = 0;
        rest[0] = 0;
​
        // 动态规划计算
        for (int i = 1; i < n; i++) {
            hold[i] = Math.max(hold[i - 1], rest[i - 1] - stocks[i]); // 保持持有或买入
            sold[i] = hold[i - 1] + stocks[i]; // 当天卖出
            rest[i] = Math.max(rest[i - 1], sold[i - 1]); // 冷冻期或休息
        }
​
        // 返回最终最大利润
        return Math.max(sold[n - 1], rest[n - 1]);
    }
​
    public static void main(String[] args) {
        // 测试样例
        System.out.println(solution(new int[]{1, 2}) == 1); // 输出:1
        System.out.println(solution(new int[]{2, 1}) == 0); // 输出:0
        System.out.println(solution(new int[]{1, 2, 3, 0, 2}) == 3); // 输出:3
        System.out.println(solution(new int[]{2, 3, 4, 5, 6, 7}) == 5); // 输出:5
        System.out.println(solution(new int[]{1, 6, 2, 7, 13, 2, 8}) == 12); // 输出:12
    }
}

4. 空间优化版本

将数组替换为滚动变量,仅需三个变量保存当前状态即可。

public class Main {
    public static int solution(int[] stocks) {
        if (stocks == null || stocks.length == 0) {
            return 0;
        }
​
        // 初始化状态变量
        int hold = -stocks[0]; // 持有股票
        int sold = 0;          // 刚卖出股票
        int rest = 0;          // 冷冻期或休息
​
        for (int i = 1; i < stocks.length; i++) {
            int prevHold = hold;
            int prevSold = sold;
​
            hold = Math.max(hold, rest - stocks[i]); // 保持持有或买入
            sold = prevHold + stocks[i];            // 卖出
            rest = Math.max(rest, prevSold);        // 冷冻期或休息
        }
​
        // 返回最终最大利润
        return Math.max(sold, rest);
    }
​
    public static void main(String[] args) {
        // 测试样例
        System.out.println(solution(new int[]{1, 2}) == 1); // 输出:1
        System.out.println(solution(new int[]{2, 1}) == 0); // 输出:0
        System.out.println(solution(new int[]{1, 2, 3, 0, 2}) == 3); // 输出:3
        System.out.println(solution(new int[]{2, 3, 4, 5, 6, 7}) == 5); // 输出:5
        System.out.println(solution(new int[]{1, 6, 2, 7, 13, 2, 8}) == 12); // 输出:12
    }
}

5. 关键总结

  1. 动态规划的核心

    • 明确问题状态和状态转移方程。
    • 使用辅助变量记录约束条件(如冷冻期)。
  2. 空间优化技巧

    • 滚动数组可以显著减少空间占用,但需谨慎避免覆盖状态。
  3. 测试用例覆盖

    • 边界条件(如空数组)。
    • 单一价格、多次涨跌等复杂情形。

6.学习方法与建议

在解决算法问题时,掌握一套系统的学习方法和实践技巧能够显著提升学习效率。以下是针对股票交易问题总结的学习方法与建议:

  1. 理解问题背景和约束

    • 在解决问题前,仔细阅读题目,明确约束条件是解题的关键。例如,本题中的冷冻期限制直接影响交易策略,需要在动态规划转移方程中体现。
    • 多用示例帮助自己理清逻辑,确保在思路上没有遗漏。
  2. 熟悉常见算法思想

    • 动态规划(DP) 是许多优化问题的核心工具,理解其状态定义、转移方程和边界条件的设计至关重要。
    • 针对 DP 问题,养成提炼“状态”和“转移关系”的习惯。本题通过设计三种状态(holdsoldrest)清晰地模拟了交易过程。
  3. 画图或列表辅助理解

    • 动态规划问题通常需要多个状态的递推,因此将状态的演变过程画图或用表格列出有助于直观理解。
    • 比如,本题可以列出每天的 holdsoldrest 值,逐步推导出状态变化的规律。
  4. 编写测试用例验证代码

  • 在完成代码后,为算法设计多样化的测试用例,包括:
- **简单测试**:如只有一天或两天数据的情况。
- **边界测试**:如空数组或极值数组(价格不变)。
- **复杂测试**:如频繁波动的股票价格。
  • 通过测试用例确保代码的正确性和鲁棒性。
  1. 总结思路并多加练习

    • 每次解决完问题后,尝试总结解决的思路和经验,并记录在学习笔记中。
    • 对于动态规划类问题,多练习类似的题目(如股票系列问题),培养对 DP 状态转移的敏感度。
  2. 与他人交流和讨论

    • 学习过程中,和同学、朋友或在线社区交流能够帮助发现自己的不足之处,也能学习到新的解题技巧。
    • 在线平台(如 LeetCode 或力扣)上可以参考其他人的优秀解答,并尝试优化自己的代码。
  3. 保持耐心,逐步提升

    • 动态规划问题通常难度较高,初学时可能会感到吃力,但只要保持耐心和练习,随着经验积累,思考和实现效率会显著提高。
    • 遇到困难时,可以参考别人的代码,但务必自己多思考,弄清楚每一步的推导过程。