最大子数组和

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

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

示例 1

复制代码
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6

解释:

复制代码
[4,-1,2,1]

的和最大:

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

示例 2

复制代码
输入:nums = [1]
输出:1

一、暴力解法

最直接的思路是枚举每一个连续子数组。

使用 i 确定子数组的起点,再使用 j 向右扩展:

复制代码
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int ans = nums[0];

        for (int i = 0; i < nums.size(); i++) {
            int tmp = 0;

            for (int j = i; j < nums.size(); j++) {
                tmp += nums[j];
                ans = max(ans, tmp);
            }
        }

        return ans;
    }
};

例如:

复制代码
nums = [1,2,3]

枚举的连续子数组为:

复制代码
i = 0:
[1]
[1,2]
[1,2,3]

i = 1:
[2]
[2,3]

i = 2:
[3]

这种方法需要两层循环,因此时间复杂度为:

复制代码
O(n²)

当数组很大时,会超出时间限制。


二、Kadane 算法

这道题可以优化到 O(n)

核心思想是:

对于当前位置 nums[i],我们只需要判断:是接在前面的连续子数组后面,还是从当前数字重新开始。

定义:

复制代码
current

表示:

以当前位置结尾的最大连续子数组和。

那么对于 nums[i],只有两种情况。

情况 1:接着前面的子数组

复制代码
current + nums[i]

情况 2:从当前位置重新开始

复制代码
nums[i]

所以:

复制代码
current = max(nums[i], current + nums[i]);

然后再用 ans 保存整个过程中出现过的最大值:

复制代码
ans = max(ans, current);

三、为什么可以舍弃前面的数字?

例如:

复制代码
nums = [4, -10, 6]

走到 -10 时:

复制代码
[4,-10] = -6

所以:

复制代码
current = -6

接下来遇到 6

如果继续带着前面的部分:

复制代码
-6 + 6 = 0

如果直接从 6 开始:

复制代码
6

显然:

复制代码
6 > 0

所以前面的 [4,-10] 可以全部舍弃。

也就是说:

如果前面一段的和已经是负数,那么它只会拖累后面的结果。

因此:

复制代码
前面的和 > 0
→ 保留,能够给后面的结果加分

前面的和 < 0
→ 舍弃,只会给后面的结果减分

四、为什么不需要"后悔"之前的选择?

关键原因是题目要求的是:

连续子数组。

假设:

复制代码
nums = [5, -2, 3]

走到 -2 时,有两个可能:

复制代码
[-2]   = -2
[5,-2] = 3

显然:

复制代码
3 > -2

因此只需要保留:

复制代码
[5,-2]

为什么可以直接放弃 [-2]

因为以后无论再接什么数字 x

复制代码
3 + x > -2 + x

例如后面接 3

复制代码
[5,-2,3] = 6
[-2,3]   = 1

原本较差的方案以后接上相同的数字,也永远不可能超过原本较好的方案。

因此只需要保留:

以当前位置结尾的最优方案。

这也是动态规划中的一个重要思想。


五、最终代码

复制代码
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int current = nums[0];
        int ans = nums[0];

        for (int i = 1; i < nums.size(); i++) {

            // 选择:
            // 1. 从 nums[i] 重新开始
            // 2. 接在前面的连续子数组后面
            current = max(nums[i], current + nums[i]);

            // 更新全局最大值
            ans = max(ans, current);
        }

        return ans;
    }
};

六、代码理解

最关键的一句:

复制代码
current = max(nums[i], current + nums[i]);

可以理解为:

复制代码
当前这个数字,是自己重新开一个子数组,
还是继续接在前面的子数组后面?

而:

复制代码
ans = max(ans, current);

表示:

复制代码
记录整个过程中出现过的最大连续子数组和。

七、复杂度分析

Kadane 算法只遍历数组一次:

复制代码
时间复杂度:O(n)
空间复杂度:O(1)

相比暴力解法:

复制代码
暴力解法:O(n²)
Kadane: O(n)

效率得到了很大提升。


总结

这道题的关键不是去枚举所有连续子数组,而是维护:

复制代码
以当前位置结尾的最大连续子数组和

每到一个新的数字,只需要判断:

复制代码
current = max(nums[i], current + nums[i]);

即:

复制代码
前面的结果有帮助 → 接着用
前面的结果拖后腿 → 从当前重新开始

最终通过:

复制代码
ans = max(ans, current);

得到整个数组中的最大连续子数组和。

相关推荐
五月底_2 小时前
LIS算法
python·算法·最长子序列
学学酱快乐2 小时前
2026年PMP考试生命周期选择决策树精讲:从需求判断到混合型统一框架的应试全攻略
算法·决策树·机器学习·pmp新版考试大纲·pmp新题型·pmp培训哪家好·pmp培训机构推荐
傅科摆 _ py2 小时前
动态规划复习
算法·动态规划
Wang's Blog3 小时前
PostgreSQL笔记49:向量检索核心算法、索引调优与过滤策略深度解析
笔记·算法·postgresql
疯狂打码的少年3 小时前
【数据结构】交换类排序:冒泡与快速排序
数据结构·笔记·算法·排序算法
嵌入式阿蔡3 小时前
面试高频考点 01:volatile / 中断 / 堆栈 八股精讲
java·面试·职场和发展·嵌入式实时数据库
Nil2084 小时前
leetcode 108有序数组转换为二叉搜索树
数据结构·算法·leetcode
高频因子挖掘机4 小时前
QuantDash 成交量单位统一实战:从“手”到“股”的跨市场量化数据清洗全流程
后端·算法·github
hn小菜鸡4 小时前
LeetCode 763、划分字母区间
数据结构·算法·leetcode