LeetCode 122:买卖股票的最佳时机 II(C)解题分析

复制代码
int maxProfit(int* prices, int pricesSize) {
    int hold = -prices[0]; //持有股票最大利润
    int noHold = 0;        //不持有股票最大利润
    for(int i=1;i<pricesSize;i++)
    {
        int newHold = noHold - prices[i];
        int newNoHold = hold + prices[i];
        hold = hold > newHold ? hold : newHold;
        noHold = noHold > newNoHold ? noHold : newNoHold;
    }
    return noHold;
}

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

语言:C

分析日期:2026-10-18

用户代码来源:用户在本次对话中直接粘贴,属于 user pasted 证据。

1. 题目要求

给定数组 prices,其中:

复制代码
prices[i] = 第 i 天的股票价格

每天可以选择买入、卖出或不操作,但任何时刻最多持有一股股票。允许进行任意多次交易,目标是最大化总利润。

题目约束:

复制代码
1 <= prices.length <= 3 * 10^4
0 <= prices[i] <= 10^4

示例:

输入 输出 说明
[7,1,5,3,6,4] 7 1 -> 5 获利 4,3 -> 6 获利 3
[1,2,3,4,5] 4 可以从 1 持有到 5,也可以逐日交易
[7,6,4,3,1] 0 没有正利润,选择不交易

2. 最关键的识别:这是"无限次交易",不是"两次交易"

题目没有规定交易次数上限。只要前一笔交易已经卖出,就可以继续寻找下一次买入机会。

例如:

复制代码
prices = [1, 5, 2, 4, 1, 6]

最佳交易为:

复制代码
1 -> 5,利润 4
2 -> 4,利润 2
1 -> 6,利润 5
总利润 = 11

因此,算法不能只寻找一个买点和一个卖点,也不能只把数组切成两段后分别求最大利润。

3. 你的两个版本在做什么

你的代码可以拆成两个部分:

3.1 maxPF:求一段区间内的一次交易最大利润

你的函数:

复制代码
int maxPF(int *prices, int pricesSize) {
    int D = *(prices), Zhi = 0;

    for (int i = 0; i < pricesSize; i++) {
        if (D > *(prices + i)) {
            D = *(prices + i);
        } else {
            Zhi = *(prices + i) - D > Zhi
                ? *(prices + i) - D
                : Zhi;
        }
    }

    return Zhi;
}

这里:

  • D 表示扫描到当前位置之前的最低价格;

  • Zhi 表示到当前位置为止的一次交易最大利润;

  • 每天要么更新最低买入价,要么尝试用当前价格卖出。

在 pricesSize >= 1 的题目约束下,这个函数的核心逻辑是正确的,时间复杂度为 O(n),额外空间复杂度为 O(1)。

它解决的是:

复制代码
只允许完成一次买入和一次卖出时的最大利润

并不是 LeetCode 122 的完整问题。

3.2 maxProfit:尝试把数组切成两段

你的 maxProfit 先统计相邻价格的下降、上升和相等次数,然后处理完全单调的特殊情况;对于普通情况,再枚举分割位置:

复制代码
前半段:用 Zhi1 记录一笔交易的利润
后半段:调用 maxPF 求一笔交易的利润
All = 两段利润之和的最大值

抽象成公式就是:

复制代码
maxProfit(prices)
≈ max over split k:
    oneTrade(prices[0..k]) + oneTrade(prices[k+1..n-1])

这实际上是在求:

复制代码
最多两次交易的最大利润

而题目需要的是:

复制代码
任意多次交易的最大利润

这不是小的实现偏差,而是问题模型不匹配。

4. 初版代码的问题

4.1 prices + i < pricesSize - 2 是错误的比较

初版中出现:

复制代码
if (prices + i < pricesSize - 2)

左侧 prices + i 是指针,右侧 pricesSize - 2 是整数。这里本来应该判断下标:

复制代码
if (i < pricesSize - 2)

指针和整数不能作为这里的逻辑条件进行比较。该表达式不是你想要的"是否还有足够元素",并且在标准 C 中不具备可移植的正确含义。

修改版将它改为:

复制代码
if (i < pricesSize - 2)

这项修改是必要的。

4.2 Zhi1 保存成了价格,而不是利润

两个版本中都有这一句:

复制代码
Zhi1 = *(prices + i) - D1 > Zhi1
    ? *(prices + i)
    : Zhi1;

条件判断使用的是:

复制代码
当前价格 - 最低价格 > 当前最大利润

但条件成立后,三目运算符保存的却是:

复制代码
当前价格

应该保存:

复制代码
当前价格 - 最低价格

最小反例:

复制代码
prices = [1, 5, 2, 4]

正确的无限次交易利润是:

复制代码
1 -> 5:4
2 -> 4:2
总利润:6

你的代码在扫描到 5 时令:

复制代码
Zhi1 = 5

然后后缀 [2,4] 的 Best = 2,于是可能得到:

复制代码
All = 5 + 2 = 7

这已经大于真实可获得的利润,说明结果不只是"没有达到最优",而是出现了不可能的虚假利润。

局部修正应为:

复制代码
Zhi1 = *(prices + i) - D1 > Zhi1
    ? *(prices + i) - D1
    : Zhi1;

但是,修正这个赋值仍然不能解决"最多两次交易"和"O(n^2)"这两个根本问题。

4.3 只枚举两个区间,无法覆盖三次及以上有利交易

反例:

复制代码
prices = [1, 5, 2, 4, 1, 6]

最佳利润是:

复制代码
(5 - 1) + (4 - 2) + (6 - 1)
= 4 + 2 + 5
= 11

你的拆分模型最多只能把这些上涨区间分配到两个部分,因此不可能表达三笔交易。

这正是本题最重要的反例:即使把 Zhi1 的赋值修好,算法仍然不符合题意。

4.4 特殊计数分支不是必需的

你的代码统计:

复制代码
int d = 0, z = 0, g = 0;

并根据下降、相等、上升的数量提前返回:

复制代码
if (z == pricesSize - 1) return 0;
if (z + d == pricesSize - 1) return 0;
if (g + z == pricesSize - 1)
    return prices[pricesSize - 1] - prices[0];

这些分支在部分单调情况下的结果是合理的,但它们没有改变算法的核心复杂度,也没有覆盖一般情况。推荐算法只需一次线性扫描,完全不需要先统计三种相邻关系。

5. 修改版比初版改善了什么

修改版将:

复制代码
if (prices + i < pricesSize - 2)

改成了:

复制代码
if (i < pricesSize - 2)

这是明确的语法语义修复,避免了指针与整数的错误比较。

但修改版仍保留:

复制代码
Zhi1 = *(prices + i) - D1 > Zhi1
    ? *(prices + i)
    : Zhi1;

因此仍然存在"把价格当成利润"的严重正确性错误。

即使进一步修正为:

复制代码
Zhi1 = *(prices + i) - D1 > Zhi1
    ? *(prices + i) - D1
    : Zhi1;

它最多也只能成为"最多两次交易"的 O(n^2) 解法,不是本题的推荐解。

6. 参考贪心解法

别人给出的代码是:

复制代码
int maxProfit(int *prices, int pricesSize) {
    int profit = 0;

    for (int i = 1; i < pricesSize; i++) {
        if (prices[i] > prices[i - 1]) {
            profit += prices[i] - prices[i - 1];
        }
    }

    return profit;
}

它的核心规则只有一句:

复制代码
只累加所有相邻的正增长

例如:

复制代码
[7, 1, 5, 3, 6, 4]

相邻差值:

复制代码
-6, +4, -2, +3, -2

只累加正数:

复制代码
4 + 3 = 7

7. 为什么逐日累加正差值是正确的

7.1 一段连续上涨可以拆成每天交易

对于连续上涨区间:

复制代码
1 -> 2 -> 3 -> 4 -> 5

从 1 买入、5 卖出的利润是:

复制代码
5 - 1 = 4

逐日交易的利润是:

复制代码
(2 - 1) + (3 - 2) + (4 - 3) + (5 - 4)
= 4

两者相等。

所以,连续上涨时,持有到波峰与每天在上涨时段完成一次买卖,利润完全相同。

7.2 下降日不应该交易

如果:

复制代码
prices[i] <= prices[i - 1]

从前一天持有到今天不会增加利润,因此当天差值贡献为 0。

对于相等价格:

复制代码
prices[i] - prices[i - 1] = 0

加不加都不影响答案。

7.3 所有上涨段的利润可以相加

把数组划分为若干个连续上涨段。每个上涨段的总利润等于该段中所有正相邻差值之和;下降段不产生正利润。

因此全局最优值就是:

复制代码
sum(max(prices[i] - prices[i - 1], 0))

8. Mermaid 控制流图

复制代码
flowchart TD
    A[开始] --> B[profit = 0]
    B --> C{i 从 1 扫描到 pricesSize - 1}
    C -->|prices[i] > prices[i-1]| D[profit += prices[i] - prices[i-1]]
    C -->|prices[i] <= prices[i-1]| E[跳过当天差值]
    D --> C
    E --> C
    C -->|扫描结束| F[返回 profit]

9. 正确性证明

设:

复制代码
delta[i] = prices[i] - prices[i - 1]

算法返回:

复制代码
P = Σ max(delta[i], 0)

对任意一段从第 l 天到第 r 天的连续上涨区间,有:

复制代码
prices[r] - prices[l]
= (prices[l+1] - prices[l])
 + (prices[l+2] - prices[l+1])
 + ...
 + (prices[r] - prices[r-1])

因此,把一次长交易拆成该上涨区间内的逐日交易,不会改变利润。

所有下降或相等的相邻差值都不会增加利润,跳过它们不会损失最优解。由于题目允许无限次交易,且每天最多持有一股,所有互不重叠的上涨段可以分别完成交易并累加。

所以:

复制代码
算法返回的正差值总和 = 所有上涨段的最大可获得利润

算法正确。

10. 复杂度比较

方法 时间复杂度 额外空间 是否符合题意 主要问题
你的 maxPF O(n) O(1) 只适合一次交易 不能处理多次交易
你的 maxProfit 最坏 O(n^2) O(1) 额外空间 否 最多两段、最多两次交易;且原代码有赋值错误
修改赋值后的两段法 O(n^2) O(1) 仍不完全符合 只能表达最多两次交易
参考贪心法 O(n) O(1) 是 无明显问题

maxProfit 的 O(n^2) 来自:

复制代码
每个分割点都调用一次 maxPF

若后缀长度分别为 n-1、n-2、...、1,总工作量约为:

复制代码
(n - 1) + (n - 2) + ... + 1 = O(n^2)

而题目要求的数组长度最多为 3 * 10^4,线性算法更合适。

11. 推荐 C 实现

复制代码
int maxProfit(int *prices, int pricesSize)
{
    int profit = 0;

    for (int i = 1; i < pricesSize; ++i) {
        if (prices[i] > prices[i - 1]) {
            profit += prices[i] - prices[i - 1];
        }
    }

    return profit;
}

如果只按题目给出的价格范围计算,最大总利润不会超过:

复制代码
(pricesSize - 1) * 10000

在 pricesSize <= 30000 时约为 2.9999 * 10^8,可以放入 32 位有符号 int。如果把函数复用到更大的生产数据范围,建议改用 long long 并重新检查返回类型。

12. 测试矩阵

测试输入 期望 覆盖点
[7,1,5,3,6,4] 7 题目示例,多段上涨
[1,2,3,4,5] 4 全程上涨
[7,6,4,3,1] 0 全程下降
[1] 0 单元素
[1,1,1,1] 0 全部相等
[1,5,2,4] 6 两次交易,检测"价格误当利润"
[1,5,2,4,1,6] 11 三次交易,检测"两段模型"
[2,3,2,4,1,5,1,6] 12 多个上涨段
[5,4,3,2,1,2,3] 2 下降后重新上涨
[0,10000,0,10000] 20000 价格边界
30000 个价格在 0 和 10000 间交替 149995000 大规模线性扫描

针对你代码的缺陷,至少应保留以下回归用例:

复制代码
[1,5,2,4]       -> 6
[1,5,2,4,1,6]   -> 11

第一组可以暴露 Zhi1 保存价格而非利润的问题;第二组可以暴露"最多两次交易"的模型限制。

13. 结论

你的 maxPF 是一个正确的一次交易子程序,但 maxProfit 把本题误解成了"把数组分成两段、每段最多交易一次"。

具体结论:

  1. 初版的 prices + i < pricesSize - 2 应改为 i < pricesSize - 2。

  2. Zhi1 的三目表达式应该保存利润差值,而不是当前价格。

  3. 即使修正上述问题,两段递归/分割思路仍然只能处理最多两次交易。

  4. 本题允许无限次交易,最简单且正确的抽象是累加所有相邻的正差值。

  5. 推荐使用参考贪心实现,时间复杂度 O(n),额外空间复杂度 O(1)。

来源

  1. LeetCode 122 官方题目描述:题意、交易规则、示例和约束。访问日期:2026-10-18。

  2. 用户在本次对话中粘贴的初版、修改版和参考 C 代码:用于个人实现评审。

相关推荐
结局无敌1 小时前
React Native for OpenHarmony 实战:三方库 react-native-modal-dialog 的鸿蒙化适配指南
深度学习·算法·react native·react.js·harmonyos
OPEN-F2 小时前
ROS2系列教程:上位机建图与导航(原理综合)
算法·机器人
weixin_307779132 小时前
C++代码实现MATLAB中的fitrtree函数功能
开发语言·c++·算法·matlab
tomwang03222 小时前
路径优化建模、优化算法分类及工程化小结
算法·数学建模·分类
小羊没烦恼!2 小时前
Ajax与JSON的一些总结
android·java·前端·数据库·mysql·算法·okhttp
2501_926978332 小时前
人机通信协议--人和机器之间的交互想象
人工智能·经验分享·算法·ai写作
行者全栈架构师2 小时前
【鸿蒙心迹】互动卡片全链路——把核心功能放到桌面(FormKit 实战)(HarmonyOS 7.x)
前端·算法·架构
无定义_2 小时前
10.11日常训练
算法
Code_Solitude2 小时前
c语言:关于qsoet函数知识点
c语言·数据结构·算法