123. Best Time to Buy and Sell Stock III

本文介绍了一种算法,用于计算给定一系列股票价格时的最大可能利润。该算法通过两次遍历股票价格数组实现:第一次从前向后寻找买入时机,第二次从后向前确定卖出时机。最终得出在允许买卖一次或多次的情况下,获取最大利润的方法。
class Solution {
public:
    int maxProfit(vector<int>& prices) {
                vector<vector<int>> maxProfits(prices.size(),vector<int>(2,0));
        int maxPos = prices.size() - 1;
        if(maxPos <= 0 ) return 0;
        maxProfits[0][0] = 0;
        maxProfits[0][1] = prices[0];//top
        maxProfits[0][2] = prices[0];//bottom
        
        for(int i=1;i<=maxPos;i++){
            //maxProfits[i][0] = max(maxProfits[i-1][0],max(maxProfits[i-1][1]-prices[i],prices[i] - maxProfits[i-1][2]));
            maxProfits[i][0] = max(maxProfits[i-1][0],prices[i] - maxProfits[i-1][2]);
            maxProfits[i][2] = prices[i]<maxProfits[i-1][2] ? prices[i] : maxProfits[i-1][2];
        }
        
        
        vector<vector<int>> maxProfits1(prices.size(),vector<int>(2,0));
        
        if(maxPos <= 0 ) return 0;
        maxProfits1[maxPos][0] = 0;
        maxProfits1[maxPos][1] = prices[maxPos];//top
        maxProfits1[maxPos][2] = prices[maxPos];//bottom
        
        for(int i=maxPos-1;i>=0;i--){
            
            maxProfits1[i][0] = max(maxProfits1[i+1][0],maxProfits1[i+1][1] - prices[i]);
            maxProfits1[i][1] = prices[i]>maxProfits1[i+1][1] ? prices[i] : maxProfits1[i+1][1];
        }
        
        int total = maxProfits1[0][0];//执行一次操作
        for(int i=2;i<=maxPos-1;i++){
            total = max(total,maxProfits[i-1][0] + maxProfits1[i][0] );
        }
        return total;
    }
    
  
};
你的代码意图是解决一个类似于“股票买卖最大收益”的动态规划问题,即在第 $ i $ 天可以卖出股票,获得收益为 `a[i] - minn[j]`(其中 `j < i`),再加上之前的最大收益 `f[j]`。但当前代码存在**多个严重错误和效率问题**,导致结果不正确或超时。 --- ### ❌ 问题分析: #### 1. **`minn` 数组逻辑错误** ```cpp minn[i] = min(minn[i-1], a[i]); ``` 你用的是 `minn[i-1]`,但外层循环中对每个测试用例都调用了: ```cpp memset(minn, INT_MAX, sizeof(minn)); ``` 这会导致 `minn[0] == INT_MAX`,而你在 `i=1` 时计算: ```cpp minn[1] = min(INT_MAX, a[1]) → 正确 ``` 但是后续的 `minn[j]` 表示的是从 `1` 到 `j` 的最小值,不是“前缀最小值”用于状态转移的合理设计。 更严重的是:你在内层循环中使用了 `minn[j]` 来表示 `[1..j]` 的最小值,这是正确的想法,但初始化方式有问题 —— 因为你每次只清空数组,却依赖于前面的状态,而 `minn[-1]` 是非法访问! > 实际上 `minn[0]` 被设为了 `INT_MAX`,然后 `minn[1] = min(INT_MAX, a[1])` 没问题,但如果 `a[1]` 很大也没事。真正的问题在于——你不需要每次都 `memset(minn, ...)` 成 `INT_MAX`,因为你是按顺序赋值的。 但更大的问题是: --- #### 2. **时间复杂度太高:O(n²) 动态规划** 你的 DP 写法: ```cpp for(int i=1; i<=n; i++) for(int j=1; j<i; j++) f[i] = max(a[i] - minn[j] + f[j], f[i]); ``` 这里 `minn[j]` 应该是 `[1..j]` 的最小值,记作 `prefix_min[j]`,这个是可以预处理的。 但你现在是想让 `f[i]` 表示以第 `i` 天结尾的最大利润。 然而标准做法是 O(n) 解决一次买卖或多次买卖,而你的 O(n²) 在 `n=1e5` 时会**严重超时**! 比如当 `t=10`, `n=1e5`,总操作数可达 $ 10 \times (1e5)^2 = 1e11 $,远超 C++ 1s 承受范围(约 1e8~1e9)。 --- #### 3. **`f` 数组未正确初始化 & 状态定义模糊** 你每次重置 `f` 为 0,意味着只能做一次交易?还是允许多次? 看表达式: ```cpp f[i] = max( a[i] - minn[j] + f[j], f[i] ) ``` 这像是“在 j 之后买入,在 i 卖出”,并加上 `f[j]`(之前的收益),说明你想支持**多次交易**,且不能重叠。 但这种写法并不是最优的。标准方法是使用两个状态: - `hold[i]`: 持有股票时的最大收益 - `sold[i]`: 不持有股票时的最大收益 或者用贪心法处理“所有上升段”。 --- #### ✅ 正确解法建议(根据常见题意推测) 假设题目是:**最多进行任意次股票买卖(买之前必须卖),求最大利润**。 例如 LeetCode 第 122 题:[Best Time to Buy and Sell Stock II](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/) 那么最优解是 **贪心:收集所有上涨段** ```cpp #include <bits/stdc++.h> using namespace std; int main() { ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); int t; cin >> t; while (t--) { int n; cin >> n; vector<int> a(n + 1); for (int i = 1; i <= n; i++) { cin >> a[i]; } long long profit = 0; for (int i = 2; i <= n; i++) { if (a[i] > a[i - 1]) { profit += a[i] - a[i - 1]; } } cout << profit << '\n'; } return 0; } ``` --- #### 如果题目是“只能进行一次买卖”? 则可以用前缀最小值优化到 O(n): ```cpp #include <bits/stdc++.h> using namespace std; int main() { ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); int t; cin >> t; while (t--) { int n; cin >> n; vector<int> a(n + 1); for (int i = 1; i <= n; i++) { cin >> a[i]; } long long max_profit = 0; int min_price = a[1]; // 前缀最小值 for (int i = 2; i <= n; i++) { max_profit = max(max_profit, (long long)(a[i] - min_price)); min_price = min(min_price, a[i]); } cout << max_profit << '\n'; } return 0; } ``` --- #### 如果题目允许两次交易(LeetCode 123)? 那就需要用两次 DP,维护正向和反向最大利润。 --- ### 🔍 总结:原代码的问题 | 问题 | 描述 | |------|------| | 时间复杂度过高 | 使用了 O(n²) 的双重循环,无法通过大数据 | | `minn` 初始化不当 | `memset` 成 `INT_MAX` 可能造成越界或误解 | | 状态转移错误 | `f[i] = max(f[j] + a[i] - minn[j])` 中 `minn[j]` 含义不清 | | 缺少边界处理 | 如 `n=0` 或 `n=1` | | 未考虑数据类型溢出 | 利润可能超过 `int` | --- ### ✅ 推荐解决方案(通用、高效、正确) 下面是一个适用于“无限次交易”情形的 **O(n)** 正确版本: ```cpp #include <bits/stdc++.h> using namespace std; int main() { ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); int t; cin >> t; while (t--) { int n; cin >> n; vector<int> prices(n); for (int i = 0; i < n; ++i) { cin >> prices[i]; } long long maxProfit = 0; for (int i = 1; i < n; ++i) { if (prices[i] > prices[i - 1]) { maxProfit += prices[i] - prices[i - 1]; } } cout << maxProfit << '\n'; } return 0; } ``` --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值