在算法与面试的经典题库中,LeetCode 53 “最大子数组和”占据着独特的枢纽地位。它以一个极其简洁的问题描述——“找到一个具有最大和的连续子数组”,引出了从朴素到精妙的多种解法,并直指动态规划思想的核心。LeetCode 053 最大子数组和动态规划的核心价值在于,它是理解动态规划状态定义、状态转移方程以及“贪心”思想在DP中体现的绝佳范例。掌握此题,不仅意味着你能高效解决一类“子数组最值”问题,更标志着你已成功跨越了从“如何解决”到“如何最优解决”的算法思维门槛。
一、问题重述:定义、约束与直观理解

给定一个整数数组 `nums`,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
关键点解析: - **子数组**:必须是原数组中连续的一段。这是与“子序列”的根本区别。 - **至少包含一个元素**:即使所有元素为负数,也必须选出一个,结果是最大的那个负数。 - **目标**:求和,而不是返回子数组本身(进阶问题会要求返回子数组位置)。
示例: 输入:nums = [-2,1,-3,4,-1,2,1,-5,4] 输出:6 解释:连续子数组 [4,-1,2,1] 的和最大,为 6。
面对这个问题,最直接的冲动是枚举所有可能的子数组。对于一个长度为 n 的数组,子数组总数为 O(n²),计算每个子数组的和需要 O(n) 时间,暴力法总复杂度高达 O(n³)。显然,我们需要更聪明的策略。
二、动态规划的核心:状态定义与“最优子结构”
动态规划的精髓是定义状态,并找到状态之间的转移关系。对于LeetCode 053 最大子数组和动态规划,最经典且高效的状态定义是:
定义状态 dp[i]:表示以第 i 个元素(nums[i])为结尾的所有连续子数组中的最大和。
这个定义至关重要。为什么是“以 i 为结尾”?因为“连续子数组”的约束,使得我们关注的是“延伸”到当前位置的情况。这种定义天然地确保了子数组的连续性。
现在,我们寻找 dp[i] 和 dp[i-1] 的关系,即状态转移方程。
三、推导状态转移方程:关键决策点
对于以 `nums[i]` 结尾的子数组,其最大和 `dp[i]` 只有两种可能:
可能性一:自立门户。 前面的子数组和 `dp[i-1]` 是负数,对我有“拖累”。那么我干脆不要前面了,自己单独作为一个子数组开始。即:`dp[i] = nums[i]`。
可能性二:接续前缘。 前面的子数组和 `dp[i-1]` 是正数,对我有“增益”。那么我肯定要接上它,这样和更大。即:`dp[i] = dp[i-1] + nums[i]`。
为了得到最大和,我们在这两种可能中取最大值。因此,状态转移方程为:
dp[i] = max(nums[i], dp[i-1] + nums[i])
这个方程是理解LeetCode 053 最大子数组和动态规划的灵魂。它简洁地表达了每一步的最优选择。同时,它也蕴含了“贪心”的思想:只要 `dp[i-1]` 对当前结果有正贡献,就保留;否则就丢弃。
四、从DP表到空间优化:O(n) 与 O(1) 的两种实现
标准动态规划实现(空间O(n)):
按照上述思路,我们需要一个长度为 n 的 `dp` 数组。`dp[0]` 初始化为 `nums[0]`(因为以第一个元素结尾的最大子数组和只能是它自己)。最终答案就是 `dp` 数组中的最大值。
public int maxSubArray(int[] nums) {
if (nums == null || nums.length == 0) return 0;
int n = nums.length;
int[] dp = new int[n];
dp[0] = nums[0];
int maxSum = dp[0];
for (int i = 1; i < n; i++) {
dp[i] = Math.max(nums[i], dp[i-1] + nums[i]);
maxSum = Math.max(maxSum, dp[i]);
}
return maxSum;
}
空间优化(空间O(1)):
观察状态转移方程,`dp[i]` 只依赖于前一个状态 `dp[i-1]`。因此,我们完全不需要维护整个数组,只需一个变量来记录前一个状态即可。这就是“滚动数组”思想,将空间复杂度降至 O(1)。
public int maxSubArray(int[] nums) {
if (nums == null || nums.length == 0) return 0;
int prevMax = nums[0]; // 相当于 dp[i-1]
int globalMax = nums[0]; // 记录全局最大值
for (int i = 1; i < nums.length; i++) {
// 计算当前的 dp[i],并同时更新“前一个状态”为当前值,供下一次迭代使用
prevMax = Math.max(nums[i], prevMax + nums[i]);
// 更新全局最大值
globalMax = Math.max(globalMax, prevMax);
}
return globalMax;
}
优化后的代码是面试中最受推崇的版本,它既体现了动态规划的思想,又具备最优的空间效率。
复杂度分析: - **时间复杂度 O(n)**:一次遍历数组。 - **空间复杂度 O(1)**:仅使用常数个变量。
五、为什么是动态规划?与贪心、分治的对比
理解LeetCode 053 最大子数组和动态规划,也需要知道其他解法的视角,这能加深你对DP独特性的认识。
1. 贪心算法视角: 上述DP的状态转移方程 `dp[i] = max(nums[i], dp[i-1] + nums[i])` 可以翻译成贪心策略:维护一个当前子数组和 `curSum`,如果 `curSum` 加上当前数后比当前数本身还小,则丢弃之前的子数组,从当前数重新开始;否则就累加。这与O(1)空间的DP实现完全等价。因此,本题的DP解法也被称为“具有贪心选择性质的DP”。
2. 分治法视角(进阶): 本题也可以用分治(线段树思想)解决,时间复杂度 O(n log n)。思路是:将数组从中间分开,最大子数组和要么在左半边,要么在右半边,要么横跨中间。横跨中间的情况需要从中间向两边扫描计算。分治法虽然效率不如DP,但其“一分为三”的思想在解决更复杂的区间问题时非常有用,也常在面试中被要求阐述。
在“鳄鱼java”网站的算法精讲板块中,这道题常常作为对比讲解DP、贪心与分治的经典案例。
六、常见误区、边界条件与面试精讲要点
编码常见误区: 1. **混淆子数组与子序列**:动态规划的状态定义必须确保连续性。 2. **初始化错误**:`dp[0]` 或 `prevMax` 必须初始化为 `nums[0]`,不能初始化为0,因为数组可能全为负数。 3. **返回值错误**:最终结果不是 `dp[n-1]`,而是 `dp` 数组中的最大值。在优化版本中,需要用 `globalMax` 持续追踪。
必须考虑的边界条件: - 数组长度为1。 - 数组元素全为负数(如 `[-2, -1, -3]`,答案是 `-1`)。 - 数组元素全为正数。
面试讲述要点: 1. **先提暴力解法及其缺点**,引出优化必要。 2. **重点阐述状态定义 `dp[i]` 为什么是以i结尾**,这是解题的关键洞察。 3. **一步一步推导出状态转移方程**,并解释两种选择的含义。 4. **写出O(n)空间代码,然后优化到O(1)空间**,展示你的优化能力。 5. **主动分析时间/空间复杂度**。
七、从本题出发:掌握最大子数组问题的变体
彻底掌握LeetCode 053 最大子数组和动态规划后,你便拥有了解决一系列变体问题的能力:
1. 返回最大子数组的起止位置(进阶): 在O(1)空间的解法中,除了维护 `prevMax` 和 `globalMax`,还需维护对应的起止索引。当 `prevMax` 被更新为 `nums[i]`(自立门户)时,重置起始位置;当 `globalMax` 被更新时,记录此时的起始位置和当前位置。
2. 允许最多翻转一次符号(LeetCode 152 最大乘积子数组 思路类似): 状态定义需要扩展,同时维护以i结尾的最大值和最小值(因为负数乘负数变正)。
3. 环形数组的最大子数组和(LeetCode 918): 两种情况:最大子数组在数组中间(同本题),或跨越了头尾(此时等于数组总和减去最小子数组和)。最终结果是两者的最大值。
4. 二维矩阵中的最大子矩阵和(LeetCode 面试题 17.24): 通过压缩行,将问题转化为多个一维的最大子数组和问题,是本题在二维空间的经典应用。
八、总结:动态规划思想的微缩模型
LeetCode 053 最大子数组和动态规划是一道近乎完美的动态规划入门与深化题目。它用最精简的模型,教会了我们:
1. **如何定义状态**:紧扣问题约束(连续),定义出有“后缀”性质的状态(以i结尾)。 2. **如何找到转移**:基于“最优子结构”,分析当前状态如何从邻近状态决策得出。 3. **如何优化空间**:识别状态依赖的局限性,进行“降维”。
这道题的最终答案,隐藏在每一步对历史是“接纳”还是“舍弃”的局部最优决策中,而动态规划正是系统地、高效地执行了这一系列决策。
现在,请你合上答案,尝试独立完成:对于一个全负数的数组 `[-3, -2, -1]`,请一步步推演动态规划的过程,并解释为什么最终答案是 `-1`。然后,思考如何修改代码,让它不仅返回最大和,还能返回这个子数组的起始和结束索引?这是对你是否真正理解状态流转的最佳检验。
版权声明
本文仅代表作者观点,不代表百度立场。
本文系作者授权百度百家发表,未经许可,不得转载。





