LeetCode 题解 53. 最大子序和(Maximum Subarray):暴力、前缀和、分治与动态规划五种解法全解析

发布时间:2026/9/19 1:58:17

LeetCode 题解 53. 最大子序和(Maximum Subarray):暴力、前缀和、分治与动态规划五种解法全解析 LeetCode 题解 53. 最大子序和Maximum Subarray暴力、前缀和、分治与动态规划五种解法全解析【免费下载链接】leetcodeLeetCode Solutions: A Record of My Problem Solving Journey.( leetcode题解记录自己的leetcode解题之路。)项目地址: https://gitcode.com/gh_mirrors/le/leetcode导读本文基于本仓库leetcode 题解集中的 53.maximum-sum-subarray-en.md及其中文对照版 53.maximum-sum-subarray-cn.md系统展开完整讲解 LeetCode 53「最大子序和」的五种解法原始暴力O(n³)、前缀和 暴力O(n²)、优化前缀和O(n)、分治法O(nlogn)与动态规划O(n)。读完本文你将掌握从暴力出发逐步优化到线性解法的完整思维路径理解前缀和与动态规划这两类核心技巧的推导过程并可直接套用仓库提供的 Java、Python3、JavaScript 三语言实现。题目回顾给定一个整数数组nums找到一个具有最大和的连续子数组子数组最少包含一个元素返回其最大和。示例输入: [-2,1,-3,4,-1,2,1,-5,4] 输出: 6 解释: 连续子数组 [4,-1,2,1] 的和最大为 6。进阶要求如果你已经实现复杂度为 O(n) 的解法尝试使用更为精妙的分治法求解。本题在仓库 README.md 的题解目录中作为数组/前缀和类题目收录是理解「连续子数组区间和」问题的经典入门题。仓库的 前缀和专题 指出当题目要求「连续」时滑动窗口与前缀和都是优化时间复杂度的重要武器而本题正是把这一思想发挥到极致的代表。解法一原始暴力枚举O(n³)TLE核心思路子数组由首尾位置(l, r)唯一确定因此先用两层for循环枚举所有可能的[l, r]组合再用第三层循环从l累加到r计算当前子数组和最后用全局变量maxSum记录最大值。这种方式代码最简单但性能极差时间复杂度为 O(n³)在 LeetCode 上必然超时TLE仅作为分析的起点。复杂度分析时间复杂度O(n³)n 为数组长度空间复杂度O(1)解法二前缀和 暴力枚举O(n²)AC核心思路暴力的瓶颈在于每次都要重新累加子数组和。引入前缀和prefixSum预处理后任意区间[l, r]的和可以在 O(1) 时间内得到subarraySum prefixSum[r] - prefixSum[l - 1]再用全局变量maxSum与每个子数组和比较maxSum max(maxSum, subarraySum)这样把时间复杂度降到 O(n²)空间换时间在 LeetCode 上可以 AC。优化提示如果不额外开数组而是直接修改原数组使其表示前缀和则空间复杂度可由 O(n) 降为 O(1)。复杂度分析时间复杂度O(n²)n 为数组长度空间复杂度O(n)前缀和数组长度 n用原数组就地改写可降至 O(1)三语言实现仓库原文代码Javaclass MaximumSubarrayPrefixSum { public int maxSubArray(int[] nums) { int len nums.length; int maxSum Integer.MIN_VALUE; int sum 0; for (int i 0; i len; i) { sum 0; for (int j i; j len; j) { sum nums[j]; maxSum Math.max(maxSum, sum); } } return maxSum; } }Python3(TLE)import sys class Solution: def maxSubArray(self, nums: List[int]) - int: n len(nums) maxSum -sys.maxsize sum 0 for i in range(n): sum 0 for j in range(i, n): sum nums[j] maxSum max(maxSum, sum) return maxSumJavaScriptfunction LSS(list) { const len list.length; let max -Number.MAX_VALUE; let sum 0; for (let i 0; i len; i) { sum 0; for (let j i; j len; j) { sum list[j]; if (sum max) { max sum; } } } return max; }解法三优化前缀和O(n)O(1) 空间解法二仍是 O(n²)能否继续优化答案是肯定的——这是解法二到解法四分治与解法五DP之间承上启下的关键一步该思路由仓库作者 lucifer 提供。核心推导定义S(i)为数组[0, i]的前缀和则区间[i, j]的和为S(j) - S(i - 1)我们只需一次遍历计算出所有的S(i)i 0, 1, 2, ..., n-1同时维护遍历到当前位置之前的最小前缀和minSum即S(k)的最小值k i那么以i结尾的最大子数组和就是maxSum max(maxSum, S(i) - minSum)其中S(i) - minSum的含义是用当前前缀和减去历史上最小的前缀和得到以当前位置结尾的、和最大的子数组。整个过程只维护两个变量minSum与maxSum不需要额外数组。复杂度分析时间复杂度O(n)n 为数组长度空间复杂度O(1)三语言实现Javaclass MaxSumSubarray { public int maxSubArray3(int[] nums) { int maxSum nums[0]; int sum 0; int minSum 0; for (int num : nums) { // prefix Sum sum num; // update maxSum maxSum Math.max(maxSum, sum - minSum); // update minSum minSum Math.min(minSum, sum); } return maxSum; } }Python3class Solution: def maxSubArray(self, nums: List[int]) - int: n len(nums) maxSum nums[0] minSum sum 0 for i in range(n): sum nums[i] maxSum max(maxSum, sum - minSum) minSum min(minSum, sum) return maxSumJavaScriptfunction LSS(list) { const len list.length; let max list[0]; let min 0; let sum 0; for (let i 0; i len; i) { sum list[i]; if (sum - min max) max sum - min; if (sum min) { min sum; } } return max; }为什么 minSum 初始为 0因为S(k)允许取空前缀k -1和为 0这保证整个数组本身从头开始的连续段也能被正确计入最大和例如数组[1, 2, 3]的最大子数组就是[1, 2, 3]本身此时minSum 0必不可少。解法四分治法O(nlogn)分治法的思想是把数组从中间一分为二最大子数组和只可能出现在三种位置完全在左半部分left nums[0]...nums[m-1]递归求解左半部分的最大子数组和完全在右半部分right nums[m1]...nums[n-1]递归求解右半部分的最大子数组和跨越中间元素nums[m]从中间元素出发向左求连续后缀最大值leftMaxSum向右求连续前缀最大值rightMaxSum跨越中点的最大和为crossMaxSum leftMaxSum rightMaxSum nums[m]最终答案取三者最大值max(left, right, crossMaxSum)下图以示例数组[-2,1,-3,4,-1,2,1,-5,4]展示了分治的分解Divide与合并Conquer过程蓝色箭头表示递归拆分橙色箭头表示逐层合并取max(left, right, cross)最终得到全局最大和 6对应子数组[4,-1,2,1]。复杂度分析时间复杂度O(nlogn)n 为数组长度。每一层的跨中点扫描需要 O(n)递归深度为 O(logn)空间复杂度仓库英文版标注为 O(1)不计递归调用栈若计入递归栈深度则为 O(logn)中文版 53.maximum-sum-subarray-cn.md 即标注为 O(logn)两版表述的差异在于是否把递归调用栈计入空间开销三语言实现Javaclass MaximumSubarrayDivideConquer { public int maxSubArrayDividConquer(int[] nums) { if (nums null || nums.length 0) return 0; return helper(nums, 0, nums.length - 1); } private int helper(int[] nums, int l, int r) { if (l r) return Integer.MIN_VALUE; int mid (l r) 1; int left helper(nums, l, mid - 1); int right helper(nums, mid 1, r); int leftMaxSum 0; int sum 0; // left surfix maxSum start from index mid - 1 to l for (int i mid - 1; i l; i--) { sum nums[i]; leftMaxSum Math.max(leftMaxSum, sum); } int rightMaxSum 0; sum 0; // right prefix maxSum start from index mid 1 to r for (int i mid 1; i r; i) { sum nums[i]; rightMaxSum Math.max(sum, rightMaxSum); } // max(left, right, crossSum) return Math.max(leftMaxSum rightMaxSum nums[mid], Math.max(left, right)); } }Python3import sys class Solution: def maxSubArray(self, nums: List[int]) - int: return self.helper(nums, 0, len(nums) - 1) def helper(self, nums, l, r): if l r: return -sys.maxsize mid (l r) // 2 left self.helper(nums, l, mid - 1) right self.helper(nums, mid 1, r) left_suffix_max_sum right_prefix_max_sum 0 sum 0 for i in reversed(range(l, mid)): sum nums[i] left_suffix_max_sum max(left_suffix_max_sum, sum) sum 0 for i in range(mid 1, r 1): sum nums[i] right_prefix_max_sum max(right_prefix_max_sum, sum) cross_max_sum left_suffix_max_sum right_prefix_max_sum nums[mid] return max(cross_max_sum, left, right)JavaScriptfunction helper(list, m, n) { if (m n) return list[m]; let sum 0; let lmax -Number.MAX_VALUE; let rmax -Number.MAX_VALUE; const mid ((n - m) 1) m; const l helper(list, m, mid); const r helper(list, mid 1, n); for (let i mid; i m; i--) { sum list[i]; if (sum lmax) lmax sum; } sum 0; for (let i mid 1; i n; i) { sum list[i]; if (sum rmax) rmax sum; } return Math.max(l, r, lmax rmax); } function LSS(list) { return helper(list, 0, list.length - 1); }实现细节提醒Java 中(l r) 1是无符号右移取中点避免l r溢出Python 的//与 JS 的同理都是向下取整Java/Python 版本的递归边界是l r时返回Integer.MIN_VALUE/-sys.maxsize保证不会干扰max计算跨中点扫描时左右两侧的起始累加值从 0 开始允许「只取中间元素一侧」的情况存在。解法五动态规划O(n)O(1) 空间动态规划的难点在于找到状态转移方程与初始状态。状态定义dp[i] - 以索引 i 结尾的最大子数组和状态转移方程dp[i] max(dp[i - 1] nums[i], nums[i])含义是以i结尾的最大子数组和要么把nums[i]续在「以i-1结尾的最大子数组」之后dp[i-1] nums[i]要么从nums[i]重新开始nums[i]两者取大。初始状态dp[0] nums[0]空间优化观察转移方程可知每一步只依赖前一个状态dp[i-1]因此不需要开长度为 n 的数组只需两个变量currMaxSum以当前位置 i 结尾的最大子数组和即dp[i]的滚动值maxSum全局最大子数组和currMaxSum max(currMaxSum nums[i], nums[i]) maxSum max(currMaxSum, maxSum)下图展示了 DP 解法在示例数组上的完整状态演变currMaxSum数组记录以每个位置结尾的最大子数组和[-2, 1, -2, 4, 3, 5, 6, 1, 5]maxSum数组记录到当前位置为止的全局最大值[-2, 1, 1, 4, 4, 5, 6, 6, 6]最终答案 6 对应子数组[4, -1, 2, 1]。复杂度分析时间复杂度O(n)n 为数组长度空间复杂度O(1)仅两个变量若不优化、使用完整 dp 数组则为 O(n)三语言实现Javaclass MaximumSubarrayDP { public int maxSubArray(int[] nums) { int currMaxSum nums[0]; int maxSum nums[0]; for (int i 1; i nums.length; i) { currMaxSum Math.max(currMaxSum nums[i], nums[i]); maxSum Math.max(maxSum, currMaxSum); } return maxSum; } }Python3class Solution: def maxSubArray(self, nums: List[int]) - int: n len(nums) max_sum_ending_curr_index max_sum nums[0] for i in range(1, n): max_sum_ending_curr_index max(max_sum_ending_curr_index nums[i], nums[i]) max_sum max(max_sum_ending_curr_index, max_sum) return max_sumJavaScript原地改写数组的紧凑写法function LSS(list) { const len list.length; let max list[0]; for (let i 1; i len; i) { list[i] Math.max(0, list[i - 1]) list[i]; if (list[i] max) max list[i]; } return max; }注意JS 版本把dp[i-1]直接覆盖写到list[i-1]上Math.max(0, list[i - 1])相当于「如果前一个状态为负则舍弃、从当前元素重新开始」与标准转移方程dp[i] max(dp[i-1] nums[i], nums[i])等价因为max(0, x) y max(y, x y)。关键点总结回顾整个推导过程五种解法层层递进暴力解枚举所有子数组首尾组合逐个求和取最大。优化手段是引入前缀和预处理把区间和查询降到 O(1)前缀和 暴力O(n²)空间换时间可作为暴力到线性解法的过渡优化前缀和一次遍历维护「当前前缀和」与「历史最小前缀和」S(i) - minSum即得到以 i 结尾的最大子数组和O(n) 且 O(1) 空间分治法从中间位置将数组一分为二分别递归求左半、右半最大子数组和再计算跨越中点的最大和三者取最大return max(leftMaxSum, rightMaxSum, crossMaxSum)动态规划找到状态转移方程dp[i] max(dp[i-1] nums[i], nums[i])与初始状态dp[0] nums[0]用两个变量滚动更新即可是理解「以 i 结尾」这类 DP 状态定义的经典范例。从 O(n³) → O(n²) → O(nlogn) → O(n) 的演进路径展示了「暴力枚举 → 预处理优化 → 分治 → 动态规划」这一完整的算法优化思维链对解决同类「连续子数组/子序列」问题具有普适的指导意义。扩展思考Follow Up仓库文档在结尾抛出了两个值得深入思考的扩展方向二维矩阵版如果输入是 M×N 的矩阵如何计算最大子矩阵的和可以从「对列做前缀和压缩、再对行跑一维最大子数组和」的思路入手把问题化归到本题乘积版如果要求最大子数组的乘积呢与最大和相比有何区别关键差异在于负数乘负数会变大因此不能只维护最大值还需同时维护最小值。仓库中的 152. 乘积最大子数组 正是这一变形的完整解答其核心关键点是「同时记录乘积最大值和乘积最小值」。相似题目152. 乘积最大子数组Maximum Product Subarray把「和」换成「积」需要同时维护最大与最小值978. 最长湍流子数组Longest Turbulent Subarray同样考察连续子数组的遍历与状态维护。延伸阅读仓库内专题动态规划专题dynamic-programming.md从记忆化递归讲起系统讲解状态转移与 DP 公式的推导方法论帮助理解解法五中dp[i]状态定义的由来前缀和专题prefix.md指出「连续」类问题中前缀和与滑动窗口对时间复杂度优化的重要意义解法二、三正是前缀和思想的直接应用本题题解目录收录位置README.md同时可参考其英文版 53.maximum-sum-subarray-en.md。【免费下载链接】leetcodeLeetCode Solutions: A Record of My Problem Solving Journey.( leetcode题解记录自己的leetcode解题之路。)项目地址: https://gitcode.com/gh_mirrors/le/leetcode创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/9/19 1:58:17

FPGA实战:将UART封装为Vivado自定义IP核全流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/19 1:53:17

HBase Shell 操作全解析:列族设计、Row Key 与排错指南

简介:这是一份HBase入门实验报告,面向正在学习Hadoop生态与NoSQL数据库的初学者,重点演示如何通过HBase Shell完成创建表、插入数据与查询操作。报告以student表为例,梳理了建表语句、put写入和get查询的具体命令,并记…

2026/9/19 2:58:21

中文字体子集化实战:从3.2MB到200KB的压缩指南

上个月给个人博客换一套中文字体时,正正经经被 3.2MB 的字体文件卡了一次。首屏加载从原先一秒出头直接飙到四五秒,移动端更是肉眼可见的白屏转圈。研究了一圈解决方案,最后用 fontTools 做了字体子集化,把整套字体从 3.2MB 压到 …

2026/9/19 2:58:21

智慧校园一卡通系统架构:协议层、事件总线与GraphQL聚合

简介:本资源是一份面向高校信息化建设者、智慧校园项目实施方及物联网系统集成商的全场景一卡通解决方案PPT,聚焦数字迎新与智能控水两大核心子系统,解决迎新流程低效、水资源粗放管理等实际痛点。文件为单个37.93MB的PPTX演示文稿&#xff0…

2026/9/19 2:53:20

401 报错 WorkBuddy 时,TaoToken 的 Key 怎么换

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/18 14:13:01

拯救者Y7000黑屏故障排查与维修实战指南

1. 项目概述:一台黑屏的拯救者Y7000,到底卡在哪一步? 联想拯救者Y7000系列笔记本,从2018年第一代搭载i5-8300H开始,到后来的i7-9750H、i7-10750H、i5-11400H,再到2023年款的R7-7840HS,它始终是学…

2026/9/19 0:03:10

验证 OpenSpec 兼容性,Cursor 的 Token 从 TaoToken 出

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/19 0:03:10

书桌角落的 Mac mini,OpenClaw 通过 TaoToken 跑任务。

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/19 0:03:10

oh-my-hermes:打造跨工具的命令编排与插件化工作流

1. 项目概述与设计初衷1.1 它到底是什么先说结论:oh-my-hermes 是一个面向开发者日常终端操作的效率工具套件,核心定位是“把分散在各类命令行工具里的高频操作,统一收拢成一套插件化、可编排的工作流”。项目灵感来源很明显——oh-my-zsh 重…

2026/9/18 14:13:03

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/18 14:13:02

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/18 14:13:02

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码