LeetCode 523中等前缀和/哈希
连续的子数组和 图解题解
这道题到底在问什么
给定非负数组 nums 与整数 k,判断是否存在长度 ≥ 2 的连续子数组,其元素和是 k 的倍数。返回 true/false。
- 输入
- nums=[23,2,4,6,7], k=6
- 输出
- true ([2,4] 和为 6)
- 输入
- nums=[1,2,12], k=6
- 输出
- false
最优解:一步一步想明白
- 3记住「前缀和取余,余数重复且间距≥2 即命中;哈希存最早下标」,下面每帧都在套它。
- 4开局先放一条「余数 0 → 下标 −1」:代表「什么都不取」的空前缀。这样从下标 0 开始的整段也能被正确匹配。
- 5扫到下标 0(值 2),更新前缀和并对 11 取余,得到余数 2。拿它去哈希表查:之前出现过吗?
- 6余数 2 是第一次见,把「余数 2 → 最早下标 0」存进哈希表(只存最早的,后面才好凑长间距)。继续下一个。
- 7扫到下标 1(值 3),更新前缀和并对 11 取余,得到余数 5。拿它去哈希表查:之前出现过吗?
- 8余数 5 是第一次见,把「余数 5 → 最早下标 1」存进哈希表(只存最早的,后面才好凑长间距)。继续下一个。
- 9扫到下标 2(值 4),更新前缀和并对 11 取余,得到余数 9。拿它去哈希表查:之前出现过吗?
- 10余数 9 是第一次见,把「余数 9 → 最早下标 2」存进哈希表(只存最早的,后面才好凑长间距)。继续下一个。
- 11扫到下标 3(值 5),更新前缀和并对 11 取余,得到余数 3。拿它去哈希表查:之前出现过吗?
- 12余数 3 是第一次见,把「余数 3 → 最早下标 3」存进哈希表(只存最早的,后面才好凑长间距)。继续下一个。
- 13扫到下标 4(值 4),更新前缀和并对 11 取余,得到余数 7。拿它去哈希表查:之前出现过吗?
- 14余数 7 是第一次见,把「余数 7 → 最早下标 4」存进哈希表(只存最早的,后面才好凑长间距)。继续下一个。
- 15扫到下标 5(值 5),更新前缀和并对 11 取余,得到余数 1。拿它去哈希表查:之前出现过吗?
- 16余数 1 是第一次见,把「余数 1 → 最早下标 5」存进哈希表(只存最早的,后面才好凑长间距)。继续下一个。
- 17扫到下标 6(值 7),更新前缀和并对 11 取余,得到余数 8。拿它去哈希表查:之前出现过吗?
- 18余数 8 是第一次见,把「余数 8 → 最早下标 6」存进哈希表(只存最早的,后面才好凑长间距)。继续下一个。
- 19扫到下标 7(值 7),更新前缀和并对 11 取余,得到余数 4。拿它去哈希表查:之前出现过吗?
- 20余数 4 是第一次见,把「余数 4 → 最早下标 7」存进哈希表(只存最早的,后面才好凑长间距)。继续下一个。
- 21扫到下标 8(值 9),更新前缀和并对 11 取余,得到余数 2。拿它去哈希表查:之前出现过吗?
- 22余数 2 之前在下标 0 出现过,间距 8 ≥ 2,它们之间这段(绿色)前缀和之差正好是 11 的倍数,找到了!返回 true。
- 23绿色这段(下标 1..8,3+4+5+4+5+7+7+9 = 44)的和是 11 的倍数,长度 ≥ 2,所以答案 true。本质就是「两处前缀和余数相同」。
⚠️ 容易写错的地方
✗ 错:哈希存余数的最新下标
✓ 对:要存最早下标
存最早才能让区间最长、最容易满足间距 ≥ 2
✗ 错:忘了初始化 {0:-1}
✓ 对:必须放,代表空前缀
否则从下标 0 开始、和恰为 k 倍数的子数组会漏判
✗ 错:只看余数相等就返回
✓ 对:还要判间距 ≥ 2
题目要求子数组长度至少为 2
完整代码(Python / C++ / Java)
Python
from typing import List
class Solution:
def checkSubarraySum(self, nums: List[int], k: int) -> bool:
first = {0: -1}
prefix = 0
for i, x in enumerate(nums):
prefix = (prefix + x) % k
if prefix in first:
if i - first[prefix] >= 2:
return True
else:
first[prefix] = i
return FalseC++
#include <unordered_map>
#include <vector>
using namespace std;
class Solution {
public:
bool checkSubarraySum(vector<int>& nums, int k) {
unordered_map<int,int> first;
first[0] = -1;
long long prefix = 0;
for (int i = 0; i < (int)nums.size(); ++i) {
prefix = (prefix + nums[i]) % k;
int r = (int)prefix;
if (first.count(r)) { if (i - first[r] >= 2) return true; }
else first[r] = i;
}
return false;
}
};Java
import java.util.*;
class Solution {
public boolean checkSubarraySum(int[] nums, int k) {
Map<Integer,Integer> first = new HashMap<>();
first.put(0, -1);
int prefix = 0;
for (int i = 0; i < nums.length; i++) {
prefix = (prefix + nums[i]) % k;
if (first.containsKey(prefix)) { if (i - first.get(prefix) >= 2) return true; }
else first.put(prefix, i);
}
return false;
}
}复杂度
时间
O(n)
扫一遍,哈希查/存 O(1)
空间
O(min(n,k))
哈希最多存 k 种余数
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 连续的子数组和 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
如果 k 可能为 0 怎么办?+
本题约束 k≥1,不必处理 0。但若允许 k=0,则「k 的倍数」退化为「子数组和为 0」,做法改成用前缀和本身(不取余)作哈希键,逻辑相同。
为什么取余能防止前缀和溢出?+
前缀和可能很大,但我们只关心它对 k 的余数,每步取余后数值始终在 [0,k) 内,既是判定依据也天然避免大数问题。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 连续的子数组和 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。