题目描述
思路解析动画文字版
思路一句话:边扫边记前缀奇数个数 odd,查「odd−k」在哈希表出现过几次就新增几段;哈希表初始放 {0:1}。下面一步步演给你看。
出发前:当前前缀奇数个数 odd = 0。哈希表先记一笔 {0: 1},代表「一个数都还没扫」这个起点。
指针 i 走到下标 0,这一格的值是 1。是奇数,前缀奇数个数 odd 要加一。
算上这一格,从开头到下标 0 这整段前缀里,奇数一共 1 个(高亮的就是当前前缀)。
要凑「恰好 2 个奇数」,就找更早出现过 odd = -1 的位置:哈希表里 -1 出现过 0 次(命中行高亮),于是新增 0 段优美子数组,累计 0 段。
最后把当前前缀奇数个数 odd = 1 记进哈希表(新增/累加的那行高亮),留给后面的位置来查。
指针 i 走到下标 1,这一格的值是 1。是奇数,前缀奇数个数 odd 要加一。
算上这一格,从开头到下标 1 这整段前缀里,奇数一共 2 个(高亮的就是当前前缀)。
要凑「恰好 2 个奇数」,就找更早出现过 odd = 0 的位置:哈希表里 0 出现过 1 次(命中行高亮),于是新增 1 段优美子数组,累计 1 段。
最后把当前前缀奇数个数 odd = 2 记进哈希表(新增/累加的那行高亮),留给后面的位置来查。
指针 i 走到下标 2,这一格的值是 2。是偶数,前缀奇数个数 odd 保持不变。
算上这一格,从开头到下标 2 这整段前缀里,奇数一共 2 个(高亮的就是当前前缀)。
要凑「恰好 2 个奇数」,就找更早出现过 odd = 0 的位置:哈希表里 0 出现过 1 次(命中行高亮),于是新增 1 段优美子数组,累计 2 段。
最后把当前前缀奇数个数 odd = 2 记进哈希表(新增/累加的那行高亮),留给后面的位置来查。
指针 i 走到下标 3,这一格的值是 1。是奇数,前缀奇数个数 odd 要加一。
算上这一格,从开头到下标 3 这整段前缀里,奇数一共 3 个(高亮的就是当前前缀)。
要凑「恰好 2 个奇数」,就找更早出现过 odd = 1 的位置:哈希表里 1 出现过 1 次(命中行高亮),于是新增 1 段优美子数组,累计 3 段。
最后把当前前缀奇数个数 odd = 3 记进哈希表(新增/累加的那行高亮),留给后面的位置来查。
指针 i 走到下标 4,这一格的值是 1。是奇数,前缀奇数个数 odd 要加一。
算上这一格,从开头到下标 4 这整段前缀里,奇数一共 4 个(高亮的就是当前前缀)。
要凑「恰好 2 个奇数」,就找更早出现过 odd = 2 的位置:哈希表里 2 出现过 2 次(命中行高亮),于是新增 2 段优美子数组,累计 5 段。
最后把当前前缀奇数个数 odd = 4 记进哈希表(新增/累加的那行高亮),留给后面的位置来查。
整趟扫完,恰好含 2 个奇数的优美子数组一共 5 段,这就是答案。
三个高频追问:odd 与哈希值的含义、为什么查 odd−k、以及和滑动窗口解法的对比。
参考代码
def numberOfSubarrays(nums, k): count = {0: 1} # 前缀奇数个数 -> 出现次数 odd = res = 0 for x in nums: odd += x & 1 # 奇数才 +1 res += count.get(odd - k, 0) # 凑出 k 个奇数 count[odd] = count.get(odd, 0) + 1 return res复杂度
- 时间:O(n),指针把数组从头到尾扫一遍,每格只查一次哈希表、记一次
- 空间:O(n),哈希表最多记 n+1 种不同的前缀奇数个数
易错点
面试追问把动画讲成自己的话
追问odd 和哈希表里的值分别代表什么?
追问为什么用 odd−k 去查,而不是 odd+k?
追问和「滑动窗口」解法比有什么区别?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
使数组和能被 P 整除
LeetCode 1590 · 中等 · 沿着 前缀和套路 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题