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

60 阅读5分钟

题目链接 : 股票市场交易优化策略

题目分析

本题要求在规定规则下计算股票交易的最大利润。具体规则如下:

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

需要我们设计一个算法,在给定的股票价格数组 stocks 下,计算能够获得的最大利润。

问题抽象

在这类问题中,核心在于如何在不违反规则的前提下,设计最优的买入和卖出策略。结合规则,问题可以分解为三个状态:

  1. 持有股票状态(hold) :表示当前手上持有股票。
  2. 未持有股票且非冷冻期状态(unfreeze) :表示手上没有股票,且可以随时购买。
  3. 冷冻期状态(freeze) :表示刚卖出股票,尚处于冷冻期,无法买入。

通过动态规划可以有效地解决该问题。

动态规划分析

动态规划是一种通过子问题求解全局问题的技术,适用于具有重叠子问题和最优子结构性质的问题。

状态定义

dp[i][0] 表示第 i 天未持有股票且不在冷冻期的最大利润; 令 dp[i][1] 表示第 i 天持有股票的最大利润。

注意:第 i 天是否处于冷冻期,可以由状态转移关系隐式表示。

状态转移方程
  1. 未持有股票状态

    • dp[i][0] = max(dp[i-1][0], dp[i-1][1] + stocks[i-1])

    • 即当天未持有股票的最大利润为:

      • 保持未持有股票状态(dp[i-1][0])。
      • 卖出手中股票获得收益(dp[i-1][1] + stocks[i-1])。
  2. 持有股票状态

    • dp[i][1] = max(dp[i-1][1], dp[i-2][0] - stocks[i-1])

    • 即当天持有股票的最大利润为:

      • 保持持有股票状态(dp[i-1][1])。
      • 从冷冻期前一天的未持有状态买入股票(dp[i-2][0] - stocks[i-1])。
边界条件
  • 第一天未持有股票且非冷冻期:dp[0][0] = 0
  • 第一天持有股票:dp[1][1] = -stocks[0]
  • 冷冻期隐含在状态转移中,无需单独表示。
目标

我们需要求解的是 dp[n][0],即最后一天未持有股票的最大利润。

代码实现

以下是动态规划方法的实现,使用二维数组存储状态:

#include <bits/stdc++.h>
using namespace std;

int solution(std::vector<int> stocks) {
    int n = stocks.size();
    if (n == 0) return 0; // 无股票可交易
    vector<vector<int>> dp(n + 1, vector<int>(2, -2e9)); // 1持有 , 0未持有

    dp[0][0] = 0;
    dp[1][0] = 0;
    dp[1][1] = -stocks[0];

    for (int i = 2; i <= n; i++) {
        dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + stocks[i - 1]);
        dp[i][1] = max(dp[i - 1][1], dp[i - 2][0] - stocks[i - 1]);
    }

    return dp[n][0];
}

int main() {
    std::cout << (solution({1, 2}) == 1) << std::endl;
    std::cout << (solution({2, 1}) == 0) << std::endl;
    std::cout << (solution({1, 2, 3, 0, 2}) == 3) << std::endl;
    std::cout << (solution({2, 3, 4, 5, 6, 7}) == 5) << std::endl;
    std::cout << (solution({1, 6, 2, 7, 13, 2, 8}) == 12) << std::endl;
    return 0;
}


算法分析

时间复杂度

动态规划的时间复杂度为 O(n),因为我们仅需遍历一次股票数组,每次计算状态转移方程。

空间复杂度

代码中使用了一个二维数组存储状态,其空间复杂度为 O(n)。通过空间优化(仅记录当前天和前两天的状态),可将空间复杂度降低至 O(1)。

扩展

下面将介绍如何将空间复杂度降至O(1)

思路分析

在原始的动态规划实现中,我们使用了一个二维数组 dp[n+1][2],来存储第 iii 天的两个状态:未持有股票(0)持有股票(1) 。但是,仔细观察状态转移方程:

  1. 计算 dp[i][0] 时,只需要用到 dp[i-1][0]dp[i-1][1]
  2. 计算 dp[i][1] 时,只需要用到 dp[i-1][1]dp[i-2][0]

这表明每次计算时,只需保留前两天的状态,而不需要整个数组。因此,可以通过变量替代数组的方式,将空间复杂度从 O(n) 降低到 O(1)。

状态变量替代

我们用以下变量分别表示特定状态:

  • prev_no_stock: 表示前一天未持有股票的最大利润(对应 dp[i-1][0])。
  • prev_with_stock: 表示前一天持有股票的最大利润(对应 dp[i-1][1])。
  • prev2_no_stock: 表示前两天未持有股票的最大利润(对应 dp[i-2][0])。

通过变量替代数组,每次将当前状态 curr_no_stockcurr_with_stock 更新为下一步的 prev_no_stockprev_with_stock,依次滚动推进。

核心代码如下

int solution(vector<int> stocks) {
    int n = stocks.size();
    if (n == 0) return 0; // 没有股票可交易

    // 初始化状态
    int prev2_no_stock = 0; // dp[i-2][0]
    int prev_no_stock = 0;  // dp[i-1][0]
    int prev_with_stock = -stocks[0]; // dp[i-1][1]

    for (int i = 1; i < n; i++) {
        // 当前状态计算
        int curr_no_stock = max(prev_no_stock, prev_with_stock + stocks[i]);
        int curr_with_stock = max(prev_with_stock, prev2_no_stock - stocks[i]);

        // 状态更新
        prev2_no_stock = prev_no_stock;
        prev_no_stock = curr_no_stock;
        prev_with_stock = curr_with_stock;
    }

    return prev_no_stock; // 最后一天未持有股票的最大利润
}

总结

本题通过动态规划的方法,将股票交易的复杂约束转化为状态转移问题,设计合理的状态和转移关系是解题的关键。这其实是一类十分经典的动态规划题目 , 在力扣中也有类似的系列,可以为初学者们更好的学习动态规划, 所以我给大家把这个系列题目也贴到下面

121. 买卖股票的最佳时机

力扣题目链接

122.买卖股票的最佳时机II

力扣题目链接

123.买卖股票的最佳时机III

力扣题目链接

188.买卖股票的最佳时机IV

力扣题目链接

309.最佳买卖股票时机含冷冻期

力扣题目链接

714.买卖股票的最佳时机含手续费

力扣题目链接