LeetCode 53 最大子数组和:一次遍历掌握 Kadane 算法

前言

"最大子数组和"是数组与动态规划中的经典题目。它的代码只有几行,但真正重要的是理解:当我们遍历到一个新元素时,什么时候应该延续前面的子数组,什么时候应该从当前元素重新开始。

这道题对应 LeetCode 53,核心解法通常称为 Kadane 算法。它既可以从动态规划角度理解,也可以看作一种基于局部最优决策的贪心过程。

一、题目说明

给定一个整数数组 nums,找出一个具有最大和的连续子数组,并返回该子数组的元素和。子数组至少包含一个元素。

例如:

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

这里最重要的限制是"连续"。我们不能像子序列问题一样跳过中间元素。

二、暴力枚举为什么不够好?

最直观的办法是枚举所有子数组的起点和终点,再计算每个区间的和。如果使用前缀和优化区间求和,仍然需要枚举两个端点,时间复杂度为 O(n²);如果每次重新累加,复杂度甚至可能达到 O(n³)

实际上,我们没有必要反复计算每个区间。遍历到当前元素时,只需要知道一个信息:以前一个位置结尾的最大子数组和,是否值得继续保留?

三、Kadane 算法的核心思想

定义两个变量:

  • current:以当前元素结尾的最大子数组和;

  • result:遍历过程中出现过的全局最大子数组和。

对于当前元素 nums[i],只有两种选择:

  1. 接在前面的子数组后面,得到 current + nums[i]

  2. 放弃前面的子数组,从当前元素重新开始,得到 nums[i]

因此状态转移公式为:

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

为什么前面的和小于零时可以直接放弃?假设此前的子数组和为 -3,当前元素为 4

复制代码
-3 + 4 = 1

显然不如直接从 4 开始。任何负数前缀都会拖累后面的结果,所以它不可能成为最优子数组的一部分。

反过来,如果此前的和为正数,它能够增大当前元素构成的子数组和,就应该继续保留。这个判断就是 Kadane 算法能够一次遍历完成计算的关键。

四、示例演算

以数组 [-2, 1, -3, 4, -1, 2, 1, -5, 4] 为例:

当前元素 current:以当前位置结尾的最大和 result:全局最大和
-2 -2 -2
1 1 1
-3 -2 1
4 4 4
-1 3 4
2 5 5
1 6 6
-5 1 6
4 5 6

遍历到 4 时,前面的 current-2,继续累加只会拖累结果,因此从 4 重新开始。随后依次加入 -1、2、1,最终得到最大和 6

注意,遍历到 -5 时,current 虽然下降为 1,但仍然是正数,所以理论上仍有保留价值。最终加入 4 得到 5,没有超过全局最大值 6

五、Java 代码实现

1. 标准动态规划写法

复制代码
class Solution {
    public int maxSubArray(int[] nums) {
        int current = nums[0];
        int result = nums[0];

        for (int i = 1; i < nums.length; i++) {
            current = Math.max(nums[i], current + nums[i]);
            result = Math.max(result, current);
        }

        return result;
    }
}

这段代码直接对应状态转移公式,含义也最清晰:当前元素要么加入原子数组,要么成为新子数组的起点。

2. 等价写法

复制代码
class Solution {
    public int maxSubArray(int[] nums) {
        int result = nums[0];
        int sum = 0;

        for (int num : nums) {
            if (sum > 0) {
                sum += num;
            } else {
                sum = num;
            }
            result = Math.max(result, sum);
        }

        return result;
    }
}

两种写法本质完全相同:

复制代码
sum > 0  → 延续原子数组
sum <= 0 → 从当前元素重新开始

标准写法更方便推导动态规划公式;截图写法则更加直观地体现了"负数前缀立即舍弃"的思想。

六、复杂度分析

  • 时间复杂度:O(n)。只需要遍历数组一次;

  • 空间复杂度:O(1)。只使用两个变量,没有额外数组。

如果使用完整的动态规划数组,令 dp[i] 表示以 nums[i] 结尾的最大子数组和,也可以求解,但每个状态只依赖前一个状态,因此可将空间从 O(n) 优化为 O(1)

相关推荐
小七在进步1 小时前
数据结构:选择排序
数据结构·算法·排序算法
Nil2081 小时前
leetcode 230二叉搜索树中第k小的元素
算法·leetcode·职场和发展
被怪兽吃掉了1 小时前
5.2.1一维组数定义方式
开发语言·c++·算法
不会就选b1 小时前
Linux之线程进阶---封装信号量
数据结构·算法
不会就选b1 小时前
算法日常・每日刷题--<BFS&&最短路径>4
算法·宽度优先
fb_123451 小时前
Shell脚本零基础通关教程|条件语句+函数+全类型循环(企业实战+面试真题)
面试·职场和发展
旖旎夜光1 小时前
LeetCode 69:x 的平方根(二分查找) —— 题解
数据结构·c++·算法·leetcode·二分查找
sel_92 小时前
【Pytorch】PyTorch 深度学习框架详解:从安装到实战
人工智能·pytorch·深度学习·算法·语言模型
学习星球2 小时前
【LeetCode算法题精讲】图算法精讲——从图遍历到拓扑排序
数据结构·算法·leetcode·图搜索