题目描述

✅ 剑指 Offer 42. 连续子数组的最大和

image-20261001230752567

image-20260928184258335

image-20260928184258336

题意分析

从非空整数数组中选择一段连续且非空的子数组,使其中元素之和最大,返回这个最大和。可以从任意位置开始、在任意位置结束,但不能跳过中间元素,也不能重排数组。

数组可能含负数,也可能全部为负。负数并不一定都要排除,因为它可能连接前后收益更大的部分;全负时仍必须选择至少一个元素,答案是其中最大的那个值,不能返回空子数组的和零。

解法:Kadane 动态规划

核心思路

[!blue]

按子数组的右端点分类。定义 cur 为必须以当前位置结尾的非空子数组最大和,ans 为已经处理过的所有右端点中的最大值。强调“必须以当前位置结尾”,才能保证下一个元素接上来时仍然连续。

处理 nums[i] 时,以它结尾的子数组只有两类:长度为一,只取 nums[i];长度至少为二,就必须接在某个以 i - 1 结尾的子数组之后。所有后一类都加上相同的当前值,因此只需保留其中最大的旧 cur,转移为 cur = max(nums[i], cur + nums[i])。

两种选择的差别只在旧 cur。旧值为负时,它会拖累当前数,应从当前数重新开始;旧值非负时,接上它不会更差。Go 的符号判断正是这个转移的等价写法,判断的是前一段的总和,而不是看到当前元素为负就立刻断开。

每得到新的 cur,再用它更新 ans。局部结尾最优值可以下降,也可以换一个起点,但全局答案要保留以前已经出现过的最佳区间。任何非空子数组都有一个右端点,所以遍历全部结尾即可覆盖全部候选。

用首元素同时初始化 cur、ans,再从第二个元素开始。这让初始状态就是一个真实非空子数组,单元素和全负输入都可以直接按同一规则处理;最终返回历史最优 ans。

解题步骤

  1. 令 cur = ans = nums[0],已经处理好只含首元素的情况。
  2. 从下标 1 开始遍历,比较单独取当前数与接在旧 cur 后面两种方案。
  3. 保存两者较大值为新 cur,再执行 ans = max(ans, cur)。
  4. 所有结尾处理完后返回 ans。

代码实现

class Solution {
    public int maxSubArray(int[] nums) {
        // 从真实元素开始,保证全负数组也只能选择非空子数组。
        int cur = nums[0];
        int ans = nums[0];

        for (int i = 1; i < nums.length; i++) {
            // 当前数单独开始,或接在此前的连续后缀之后。
            cur = Math.max(nums[i], cur + nums[i]);
            ans = Math.max(ans, cur);
        }

        return ans;
    }
}
func maxSubArray(nums []int) int {
    // 从真实元素开始,保证全负数组也只能选择非空子数组。
    cur := nums[0]
    ans := nums[0]

    for i := 1; i < len(nums); i++ {
        // 旧后缀为负时会拖累当前数,因此从当前数重新开始。
        if cur < 0 {
            cur = nums[i]
        } else {
            cur += nums[i]
        }
        if cur > ans {
            ans = cur
        }
    }
    return ans
}

复杂度分析

  • 时间复杂度:$O(n)$,每个元素只进行一次常数时间的状态转移。
  • 空间复杂度:$O(1)$,下一步只依赖前一位置的 cur,无需保存整张动态规划表。

关键点总结

[!green]

  • 以当前位置结尾的最优和保证了后续拼接的连续性。
  • 每步只有重新开始或接上旧后缀两类选择,旧后缀为负时应舍弃。
  • cur 负责当前结尾,ans 保留所有结尾的最好结果,并以真实首元素初始化。

易错点总结

[!yellow]

  • 用零初始化答案或允许把局部结果归零,会让全负数组错误地选择空区间。
  • 直接把当前数接到历史 ans 后面,无法保证那段历史最优区间紧邻当前位置。
  • 只累加正数,或遇到负数就截断,会破坏连续性或丢掉需要跨过少量负数的最优区间。
  • 最后返回 cur,会漏掉较早结束的最佳区间。
  • 首元素已经用于初始化,循环必须从下标 1 开始,避免重复计入它。

相似题目

题目 难度 关联与区别
918. 环形子数组的最大和 中等 在最大连续和基础上增加首尾相接的情况,还要考虑总和减最小连续段。
152. 乘积最大子数组 中等 同样维护以当前位置结尾的最优状态,但负数乘法会交换大小,需同时记录最大与最小乘积。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/84853285
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!