#53 一维DP 中等

最大子数组和

在 LeetCode 上查看 ↗

🎧 语音讲解

开车或通勤时可听,跟着思路走一遍

速度

📋 题目描述

给你一个整数数组 nums,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

子数组 是数组中的一个连续部分。

示例 1

输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
连续子数组 [4,-1,2,1] 的和最大,为 6。

示例 2

输入:nums = [1]
输出:1

示例 3

输入:nums = [5,4,-1,7,8]
输出:23
整个数组即为最大子数组,和为 23。

💭 模拟答题者思考

1. 最直接:枚举所有连续子数组 [l..r],对每个区间求和取最大,双重循环 O(n²),n=10⁵ 会超时。

2. 重复在哪里?固定右端点 r 时,左端点 l 从 0 到 r 的区间和 sum(l,r) 可以从前一个 sum(l,r-1) 加上 nums[r] 得到——但更简单的是只关心「以 r 结尾」的最优子数组。

3. 子问题定义:设 dp[i] = 以 nums[i] 结尾的连续子数组的最大和。则 dp[i] = max(nums[i], dp[i-1] + nums[i])——要么单独成段,要么接在前一段后面。

4. 全局答案不在 dp[n-1],而是 max(dp[0..n-1]):最优子数组可能结束在任意位置(如样例中结束在下标 6 而非末尾)。

5. 手推 [-2,1,-3,4,-1,2,1,-5,4]cur 依次为 -2→1→-2→4→3→5→6→1→5,ans 在扫到 6 时取到最大值 6,对应子数组 [4,-1,2,1]。

🧠 变量语义(先读这三句再编码)

变量类型语义(三句法)
curint定义:以当前下标 i 结尾 的连续子数组的最大和
维护:每扫到一个新元素,要么「接上前面的子数组」,要么「从当前元素重新开始」——取两者较大值
更新cur = max(nums[i], cur + nums[i]),即 Kadane 核心递推
ansint定义:遍历过程中见过的所有「以某位置结尾的子数组」中的全局最大和
维护:每更新一次 cur,同步 ans = max(ans, cur)
更新:初始 ans = nums[0](至少含一个元素),扫完返回 ans
iint定义:从左到右扫描的下标,代表「当前考察的结尾位置」
维护for i in range(1, n),第 0 个元素已用于初始化 curans
更新:每轮用 nums[i] 更新 cur,再刷新 ans

⌨️ 落码步骤

1. 初始化 cur = ans = nums[0](子数组至少含一个元素)

2. 从下标 1 遍历到 n-1

3. 对每个 nums[i]cur = max(nums[i], cur + nums[i])(接上 or 重启)

4. 更新全局:ans = max(ans, cur)

5. 返回 ans

💻 代码实现

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        cur = ans = nums[0]
        for i in range(1, len(nums)):
            cur = max(nums[i], cur + nums[i])
            ans = max(ans, cur)
        return ans
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int cur = nums[0], ans = nums[0];
        for (int i = 1; i < nums.size(); i++) {
            cur = max(nums[i], cur + nums[i]);
            ans = max(ans, cur);
        }
        return ans;
    }
};
// 时间 O(n),空间 O(1)

📈 复杂度分析

时间复杂度 O(n)
空间复杂度 O(1)

⚠️ 常见坑

答案不是 dp[n-1]:最大子数组可以结束在任意位置,必须全程维护 ans = max(ans, cur),不能只返回最后一次的 cur

全负数数组:如 [-3,-2,-1]cur 会不断被 max(nums[i], ...) 重置为当前元素,ans 应取最大的那个负数(-1),不能返回 0。

初始化勿用 cur=0:子数组至少包含一个元素,应从 nums[0] 开始;若 cur 初值为 0,全正数组虽能蒙对,全负时会错成 0。

🔍 必测边界 Case

Case 1:单元素
nums = [1] → 1(唯一子数组即自身)
Case 2:全负数
nums = [-3, -2, -1] → -1(必须选一个元素,取最大负数)
Case 3:全正数
nums = [5, 4, -1, 7, 8] → 23(整个数组即最优,无需截断)
Case 4:中间最优段
nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4] → 6(最优子数组不贴首尾)
Case 5:前缀拖累
nums = [-1, -2, 5, -1, 3] → 7(前面负前缀应被丢弃,从 5 重启)