题目描述
思路解析
一句话答案:LeetCode 918 环形子数组的最大和用双 Kadane:答案两情形取大——不绕环是普通最大子数组和,绕环等于总和减去最小子数组和;一趟扫描同时跑最大、最小两个 Kadane,全负时特判返回最大和。时间 O(n)、空间 O(1)。
环形子数组的最大和在找什么
给一个整数数组 nums,首尾相连成环,最后一个后面接回第一个。要在环上找一段连续、非空的子数组让和最大,可从末尾绕过去接开头,每个下标最多用一次。比如 nums=[1,-2,3,-2] 不绕环取 [3] 得 3;nums=[5,-3,5] 则末尾 5 绕回接开头 5、跨过 -3,和为 10。
为什么不能把环剪断暴力枚举
把环剪断枚举所有连续段求和要 O(n²) 段,一大就吃不消。也有人复制成 2n 长再跑普通最大子数组,但得限制选段长度不超过 n,否则同一下标算两次,还要配前缀和加单调队列,繁。突破口是:环上最优段只有两种长相,分开处理一趟扫描就够。
绕环的最大,等于总和减去最小子数组
把答案分成互斥两类。第一类不绕环,就是普通连续子数组,正是最大子数组和问题(LeetCode 53):一个 Kadane 一趟扫描就能求出,每步只决定把当前元素接前段后面、还是自己另起一段,记为 bestMax。
第二类绕环,选「末尾一截 + 开头一截」,中间空一段没选。total 固定,绕环选中的和 = total 减中间那段;想让它最大,就得让挖掉那段最小。所以绕环最大和 = total 减最小子数组和,同样用 Kadane 求,只把每步 max 换成 min,记为 bestMin。
为什么两个 Kadane 一趟就能同时算完
两个 Kadane 各维护 max_end、min_end(以当前元素结尾那段的最大、最小和)和 bestMax、bestMin(至今全局最优)。它们互不干扰,一个循环扫一遍全更新。答案取 bestMax 与 total - bestMin 里更大的:前者管不绕环,后者管绕环,取大即全局最优。
拿 [5,-3,5] 亲手算一遍
total = 5 + (-3) + 5 = 7,四个量起点都设成 nums[0]=5。到 -3:最大段接前面 5 得 2 比单飞大,max_end=2、bestMax 仍 5;最小段 -3 单飞更小,min_end=-3、bestMin=-3。到第二个 5:最大段接前面 2 得 7,max_end=7、bestMax=7;最小段接上得 2 更小,min_end=2、bestMin 仍 -3。收尾 bestMax=7、bestMin=-3,答案 max(7, 7-(-3)) = 10。total 减最小段 -3,正是挖掉中间 -3、留两头两个 5 相接的和。
复杂度多少,全负数组这个坑
求 total 一趟、双 Kadane 一趟都是线性,时间 O(n);全程只用几个标量滚动,空间 O(1)。
最阴的是全负数组 nums=[-3,-2,-3]:total=-8,最小子数组是整个数组,bestMin=-8。照搬 total - bestMin = 0,等于把整个数组挖掉、剩和为 0 的空段,可题目要求非空,0 取不到。所以先看 bestMax:它 < 0 说明全负,绕环的 0 不合法,返回 bestMax(这里 -2)。另一坑是只跑最大 Kadane,绕环整类全丢,[5,-3,5] 会误答成 7。
▶ 动画逐步走查(文字版)——想跟着上方动画一帧帧对照就展开
一句话:同时跑两个 Kadane(一个求最大段、一个求最小段),答案 = max(bestMax, total - bestMin),全负时只取 bestMax。
先把整个数组的和 total=2 一次性求出来(后面是个常量);再让两个 Kadane 都从第 0 个元素 5 起步:绿色是「正在生长的最大段」,红色是「正在生长的最小段」,此刻都只含 5。
看第 1 个 -2(紫)。先问绿色「最大段」:把 -2 接到前段(5)得 3,还是 -2 单飞更大?
接上去更大,绿色段延伸,maxEnd=3。没超过 bestMax=5,保持。
同一个 -2,换红色「最小段」视角:接上前段(5)得 3,还是 -2 单飞更小?
单飞更小,红色段从 -2 重新开始,minEnd=-2。刷新 bestMin=-2。
看第 2 个 3(紫)。先问绿色「最大段」:把 3 接到前段(3)得 6,还是 3 单飞更大?
接上去更大,绿色段延伸,maxEnd=6。超过旧 bestMax,刷新 bestMax=6。
同一个 3,换红色「最小段」视角:接上前段(-2)得 1,还是 3 单飞更小?
接上去更小,红色段延伸,minEnd=1。没小过 bestMin=-2,保持。
看第 3 个 -8(紫)。先问绿色「最大段」:把 -8 接到前段(6)得 -2,还是 -8 单飞更大?
接上去更大,绿色段延伸,maxEnd=-2。没超过 bestMax=6,保持。
同一个 -8,换红色「最小段」视角:接上前段(1)得 -7,还是 -8 单飞更小?
单飞更小,红色段从 -8 重新开始,minEnd=-8。刷新 bestMin=-8。
看第 4 个 4(紫)。先问绿色「最大段」:把 4 接到前段(-2)得 2,还是 4 单飞更大?
单飞更大,绿色段从 4 重新开始,maxEnd=4。bestMax 仍是 6。
同一个 4,换红色「最小段」视角:接上前段(-8)得 -4,还是 4 单飞更小?
接上去更小,红色段延伸,minEnd=-4。没小过 bestMin=-8,保持。
先看不绕环:最大子数组是绿色这段(5+-2+3 = 6)。这是候选 A。
再看绕环:要绕环,就等于「挖掉中间一段、留两头」。挖掉的越小越好 → 挖掉红色这段最小子数组(和 = -8)。
挖掉红段后,剩下两头(绿色)绕环接成一段:2 - (-8) = 10。这是候选 B,正是末尾绕回开头的那段。
两个候选取大:B 更大,绕环胜出,答案 = 10(高亮即最终选中的那段)。
边界先想清:单元素、全负(取最大单个)、全正(整段)。
两个高频追问:与 LC53 的关系,以及单调队列的替代解法。
参考代码
from typing import Listclass Solution: def maxSubarraySumCircular(self, nums: List[int]) -> int: total = sum(nums) max_end = min_end = nums[0] best_max = best_min = nums[0] for i in range(1, len(nums)): x = nums[i] max_end = max(x, max_end + x) best_max = max(best_max, max_end) min_end = min(x, min_end + x) best_min = min(best_min, min_end) return best_max if best_max < 0 else max(best_max, total - best_min)复杂度
- 时间:O(n),先求一遍 total,再一趟扫描同时跑两个 Kadane,都是线性
- 空间:O(1),只用 maxEnd/minEnd/bestMax/bestMin/total 几个标量
易错点
面试追问把动画讲成自己的话
追问和普通的「最大子数组和」(LC53)有什么关系?
追问除了双 Kadane,还有别的思路吗?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
将字符串翻转到单调递增
LeetCode 926 · 中等 · 沿着 动态规划套路 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题