30 秒识别Q1 · 383 Ransom Note:判断能否用杂志上的字母拼出勒索信(每个字母只能用一次)。你的第一反应是哪个模式?
看答案
哈希。「每个字母只能用一次」= 计数比较,Counter(note) <= Counter(magazine)。看到「次数够不够」就是 Counter,不是滑窗——它根本不要求连续。
写题时你真正在做的动作只有三种:见过没有(set)、见过几次(Counter)、见过的东西挂在哪(dict → 下标 / 列表)。先想清楚要问哪一种,代码几乎是自动的。
它的典型用法是把一层循环换成一次查表:暴力解里那个「回头去前面找有没有某个数」的内层循环,就是哈希表的入口。代价是内存,以及顺序信息全丢了——需要顺序的题(第 K 大、区间和)得另找工具。
| 题面出现… | 想到 |
|---|---|
| 「是否存在 / 有没有重复 / 出现过吗」 | set |
| 「出现次数 / 频率 / 众数 / anagram」 | Counter |
| 「两数之和 / 配对 / 补数」且数组无序 | dict 存「我需要的另一半」(模板 A) |
| 「分组 / 归类 / 把同类放一起」 | dict[签名] → list(模板 B) |
| 题目要求 O(n),而你的暴力解是两层循环在「回头找」 | 哈希把内层循环换成一次查表 |
| 「最长连续序列」但不要求元素相邻 | set + 只从序列起点向右扩 |
同样是「两数之和」,数组无序才用哈希;已排序就该用双指针(第 02 章),省掉 O(n) 空间。「子数组和恰好为 K」表面像哈希,实际主语是前缀和(第 13 章),哈希只是配件。「频率最高的 K 个」先用 Counter 数完,选 K 个的动作属于堆(第 08 章)。
■ 【骨架】 不变,闭着眼默写 ■ 【插槽】 随题变化,写题前先问自己填什么
def two_sum(nums, target):
seen = {} # 【骨架】值 -> 下标,记录"我来过"
for i, x in enumerate(nums): # 【骨架】
need = target - x # 【插槽】"另一半"怎么算
if need in seen: # 【骨架】先查后存,否则会拿自己配自己
return [seen[need], i] # 【插槽】答案形式:下标对 / 计数 / True
seen[x] = i # 【骨架】
return []
[3,3] 这种用例会拿自己配自己。from collections import defaultdict
def group_by_signature(words):
buckets = defaultdict(list) # 【骨架】签名 -> 同组成员
for w in words: # 【骨架】
key = tuple(sorted(w)) # 【插槽】签名怎么算:排序 / 计数向量 / 归一化
buckets[key].append(w) # 【骨架】
return list(buckets.values()) # 【插槽】要分组本身 / 组数 / 最大的那组
set 的最小用法。顺手记住变体:len(set(nums)) < len(nums) 一行解决。Counter(s) == Counter(t)。面试官常追问排序解法 O(n log n) 与计数解法 O(n) 的取舍,以及 Unicode 字符集下 26 长度数组会失效。tuple(sorted(w)) 或 26 维计数元组——后者 O(n·k),前者 O(n·k log k),说得出这个差别就够了。set,只从「左边没有邻居」的数开始向右扩。这一条剪枝把 O(n²) 压回 O(n),见翻车点④。set,用 (i // 3, j // 3) 当宫的键。骨架不变,键变了。if need in seen 必须在 seen[x] = i 之前。反过来写,[3,3] target=6 会返回 [0,0]。set 就够。别默认写 dict——set 的意图更清楚,读代码的人一眼知道你不关心位置。Counter 的删空问题——滑动窗口里用 Counter 当窗口状态时,减到 0 的键不会自动消失,len(cnt) 会把它算进去。要么 del,要么另外维护一个「不同字符数」。(== 比较是个例外:Python 3.10 起它会忽略零计数,细节见第 03 章翻车点④。)[1,2,3,...,n])。正确写法是先判断 x - 1 not in s,只有序列起点才进入扩展循环。list 不能当键,tuple 可以;dict 不能,frozenset 可以。分组题里签名一律用元组。30 秒识别Q1 · 383 Ransom Note:判断能否用杂志上的字母拼出勒索信(每个字母只能用一次)。你的第一反应是哪个模式?
哈希。「每个字母只能用一次」= 计数比较,Counter(note) <= Counter(magazine)。看到「次数够不够」就是 Counter,不是滑窗——它根本不要求连续。
30 秒识别Q2 · 219 Contains Duplicate II:判断数组中是否存在两个相同的数,且它们的下标之差不超过 k。你的第一反应是哪个模式?
哈希。存「值 → 最近一次出现的下标」,边走边比较距离,一次遍历。题面里的 k 是个障眼法:要问的是「见过吗」,不是「区间和」也不是「有序数组里的一对」。
30 秒识别Q3 · 给两个已排序的数组,把第二个合并进第一个(原地,第一个数组尾部有足够空位)。你的第一反应是哪个模式?
双指针,不是本章——数组已排序,用哈希既浪费空间又丢掉顺序这个最有价值的条件。见 §2 分流条件:有序是双指针的入场券(第 02 章)。
每章自测都会混入恰好一道非本章题,防止「读哪章选哪章」的条件反射;题面也一律取自本章未收录的题,避免凭记忆而非凭信号作答。