C++中求和为K的最长子数组最优解是前缀和配合unordered_map记录各前缀和首次出现下标,时间复杂度O(n),空间O(n);需初始化map[0]=-1,只在首次出现时插入前缀和以保证子数组最长。

用前缀和 + 哈希表一次遍历解决
直接说结论:C++ 中找和为 K 的最长子数组,最优解是用前缀和配合 std::unordered_map 记录每个前缀和首次出现的下标,时间复杂度 O(n),空间 O(n)。暴力枚举所有子数组(O(n²))在 n > 10⁴ 时会超时,不推荐。
核心思路是:若子数组 nums[i..j] 和为 K,则前缀和满足 prefix[j+1] - prefix[i] == K,即 prefix[i] == prefix[j+1] - K。我们边算前缀和,边查有没有更早出现过的 prefix[i] 满足该等式——有,就更新最大长度。
- 必须记录「首次」出现的下标,才能保证子数组最长(相同前缀和越靠前,子数组越长)
- 初始化时要插入
prefix[0] = 0在下标-1(用map[0] = -1),否则会漏掉从索引0开始的合法子数组 - 不要用
std::map,它按 key 排序,无谓增加O(log n)开销;unordered_map足够且更快
代码实现注意边界与初始化
常见错误是忘记处理前缀和为 K 的情况(即从开头到当前位置和就是 K),或错把下标当长度算。下面是最简健壮写法:
int longestSubarrayWithSumK(const vector<int>& nums, int K) {
unordered_map<int, int> first_occurrence;
first_occurrence[0] = -1; // prefix sum 0 at index -1
int prefix = 0, max_len = 0;
<pre class="brush:php;toolbar:false;">for (int i = 0; i < nums.size(); ++i) {
prefix += nums[i];
int target = prefix - K;
if (first_occurrence.find(target) != first_occurrence.end()) {
max_len = max(max_len, i - first_occurrence[target]);
}
// 只在第一次遇到该 prefix 时记录,保证最长
if (first_occurrence.find(prefix) == first_occurrence.end()) {
first_occurrence[prefix] = i;
}
}
return max_len;}
立即学习“C++免费学习笔记(深入)”;
关键点:
- 用
i - first_occurrence[target]算长度,不是i - target或其他变体 -
first_occurrence只在未存在时插入,避免覆盖更早位置 - 输入为空时返回
0,符合语义(空子数组和为 0,但题目隐含非空?需看题意;本实现按标准定义处理)
负数、零、重复前缀和都天然支持
这个方法不要求数组元素为正,对负数、零完全兼容——因为只依赖前缀和差值,不依赖单调性。重复前缀和(如多次出现 prefix == 5)也不会干扰,因为我们只保留第一个位置。
但要注意:K 本身可以是负数或零。例如 K == 0 时,就是在找和为 0 的最长子数组,逻辑不变;测试时可用 nums = {1, -1, 0, 1}, K = 0 验证是否返回 3(子数组 {1,-1,0})。
- 如果题目要求返回子数组本身而非长度,只需额外记录起止下标,不改变主干逻辑
- 若内存极度受限(如嵌入式),可考虑用排序 + 二分的离散化方案,但通常没必要
- LeetCode 上类似题(如 560、974)都适用此模板,稍改条件即可复用
为什么不用滑动窗口?
滑动窗口(双指针)仅适用于「元素非负」且「和单调递增」的场景。一旦数组含负数,右移左指针不能保证和减小,窗口无法收缩,会漏解。例如 nums = {2, -1, 2}, K = 3,最长解是整个数组(长度 3),但滑动窗口在 i=0,j=0 得和 2,j=1 得 1,j=2 得 3 → 此时若左指针右移就丢掉正确解。
所以只要题目没明确说「非负整数」,就别碰滑动窗口。前缀和 + 哈希表是通用解,且不易写错。
最容易被忽略的是初始化 map[0] = -1 这一行——少它,所有以索引 0 开头的解都会丢失,而且这种 bug 不易通过小样例发现。


















