1. 先看清题目:这题不是普通“子数组最大和”
LeetCode 2461 这道题,我第一次刷的时候,并没有觉得它难:无非找一个长度为 K 的连续子数组,求最大和。可写完两个版本都错,才意识到真正的题眼不是“最大”,而是“子数组内所有元素互不相同”。这题的常规解法骨架是定长滑动窗口 + 哈希表;窗口负责把“连续子数组的和”从每次重算变成增量更新,哈希表负责回答“当前窗口内到底有没有重复元素”。这篇文章就用这一道题,把这种组合套路拆开讲清楚,顺便记录几个我实际提交时踩过的坑。
题面很短:给你一个整数数组 nums 和一个整数 k,找一个长度为 k 的连续子数组,并且这个子数组内部的所有数字都不同。返回所有满足条件的子数组中,元素和的最大值。如果没有这样的子数组,返回 0。
1.1 限制条件里藏着两个“与”关系
第一眼看过去很容易只盯着“最大和”,于是脑子里马上跳出最大子段和、前缀和、贪心之类的方案。但仔细拆解,条件其实是:
- 长度必须正好是
k; - 窗口内不能有重复值;
- 满足前两条的子数组,再取最大和。
这三个条件不是互相替代关系,而是“与”关系。比如 k = 3,数组是:
text复制[1, 5, 4, 2, 9, 9, 9]
第一个长度为 3 的子数组 [1,5,4] 合法,和是 10;第二个 [5,4,2] 合法,和是 11;第三个 [4,2,9] 合法,和是 15。到了 [2,9,9],虽然数组仍然连续,和也是 20,但因为有重复的 9,整段直接作废。
所以“最大和”是在一堆合法候选里找最大,而不是直接用滑动窗口求所有长度为 k 的子数组最大和再回头校验。
1.2 为什么一定要坚持“连续子数组”
题目说子数组,不是子序列,也不是任意选 k 个位置。这意味着问题天然适合滑动窗口,而不是排序或者组合。如果题目改成“从数组中任意挑 k 个互不相同的数,使和最大”,那解法就变成另外一套:按数值排序、从大到小加和,顺便判断数值种类是否够 k 个。
正因为是连续子数组,位置关系被锁死,我们可以让窗口从左往右走。每一次移动只丢出一个旧元素、加入一个新元素,前后的窗口有大量重叠,这个重叠就是优化的最大突破口。这也是“定长滑窗”这类题目最关键的特征:窗口长度固定,移动成本必须做成增量式。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 暴力解法为什么会被卡,以及省掉的成本到底在哪
学算法题最容易犯的错,不是不知道高级解法,而是低估暴力解法的开销。LeetCode 2461 的数组长度能到 10^5,如果每次枚举一个起点,再重新检查窗口内的重复情况,复杂度很容易变成 O(nk)。
2.1 最直白的写法:嵌套循环加集合
很多初学者第一次会这样写:
- 枚举左端点
l,从0到n-k; - 枚举
l到l+k-1,累加和; - 用一层循环或者一个集合判断有没有重复;
- 如果没重复,和最大值比较。
核心代码类似这样:
python复制def maximum_subarray_sum_brute(nums, k):
n = len(nums)
ans = 0
for l in range(n - k + 1):
window = nums[l:l + k]
if len(set(window)) == k:
ans = max(ans, sum(window))
return ans
这段代码非常直观,但它做了三件重复的事:
- 每个窗口都重新累加一遍和,两个相邻窗口之间有
k-1个重叠元素被重复加; - 每个窗口都重新建集合,重复判断又消耗一轮;
set(window)的开销还和k有关,窗口越长越慢。
2.2 相邻窗口之间能复用什么
我们来看最核心的观
