LeetCode 242简单哈希计数
有效的字母异位词 图解题解
两个字符串字母一模一样只是顺序不同?一张 26 格的账本,一次对消就能验出来。
想象你要核对两份采购清单是否完全一样。先按清单 s 逐项在账本里记上去(苹果+1、香蕉+1……);再拿清单 t 逐项对消(苹果-1、香蕉-1……)。两张单走完,26 个格子全归零,说明完全一致;任何一格不是零,说明有差异。这道题的字母计数器,干的就是这件事。
这道题到底在问什么
判断两个字符串是否由完全相同的字母组成。
- s
- "anagram"
- t
- "nagaram"
- 输出
- true
最优解:一步一步想明白
- 3把两个字符串排序再比较是常见做法。小写字母只有 26 种,用计数更贴题。
- 4同一个字母在 s 里出现一次就加一,在 t 里出现一次就减一。完全抵消就是异位词。
- 5len(s) 等于 len(t) 才继续长度不同直接 false。长度相同才有机会全部抵消。
- 6s[0]=a 加账,t[0]=n 减账同一步里:s 的 a 加一格、t 的 n 减一格,是一升一降同时发生。账本开始变化。
- 7s[1]=n 加账,t[1]=a 减账下一对是 n 和 a,刚好把前面的账抵消掉。
- 8走到第 4 对 (g,a),账面又抵平走到 g 这一对,g 和前面 s 里多出来的 g 名额抵平,账面又全归零。每个位置都做一次加一减一,不关心顺序,只关心总次数。
- 9扫完,26 个计数全为 0最后 26 个计数都回到 0,说明两个词字母账完全一样。
- 10s=rat 加账 / t=car 减账,扫完看余额s=rat 给 r a t 各加一,t=car 给 c a r 各减一。r 和 a 抵平归零,但 s 多出的 t 和 t 串多出的 c 配不上对,账没清零,返回 false。
- 13顺序不重要,次数完全相同才重要。
- 16s=aab 与 t=ab:种类相同次数不同aab 和 ab 的字符集合都是 {a,b},但 a 的次数不同,不能只比 set。
- 23这题学完不要乱跳,下一步去 对应专题 连刷同类题;卡住时用 AI 答疑追问“为什么这一步成立”,比只背答案更稳。
⚠️ 容易写错的地方
✗ 错:忘记先判断长度
✓ 对:长度不同直接 false
长度不同不可能完全抵消。
✗ 错:用 set 比较字符种类
✓ 对:必须比较出现次数
aab 和 ab 字符种类相同但不是异位词。
✗ 错:只支持小写却写成通用 Unicode
✓ 对:按题目约束选 26 数组
更简单也更快。
完整代码(Python / C++ / Java)
Python
class Solution:
def isAnagram(self, s, t):
if len(s) != len(t):
return False
cnt = [0] * 26
for a, b in zip(s, t):
cnt[ord(a) - ord('a')] += 1
cnt[ord(b) - ord('a')] -= 1
return all(x == 0 for x in cnt)C++
class Solution {
public:
bool isAnagram(string s, string t) {
if (s.size() != t.size()) return false;
vector<int> cnt(26, 0);
for (int i = 0; i < s.size(); i++) {
cnt[s[i] - 'a']++;
cnt[t[i] - 'a']--;
}
for (int x : cnt) if (x != 0) return false;
return true;
}
};Java
class Solution {
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) return false;
int[] cnt = new int[26];
for (int i = 0; i < s.length(); i++) {
cnt[s.charAt(i) - 'a']++;
cnt[t.charAt(i) - 'a']--;
}
for (int x : cnt) if (x != 0) return false;
return true;
}
}套路模板 · 计数对冲骨架(可背)
# 计数对冲:A 串加、B 串减,最后看账本是否全 0
if len(A) != len(B): return False # 先卡长度
cnt = [0] * 字符域大小 # 小写字母=26
for x, y in zip(A, B):
cnt[索引(x)] += 1 # A 出现:加
cnt[索引(y)] -= 1 # B 出现:减
return all(v == 0 for v in cnt) # 全抵消才相等if (A.size() != B.size()) return false;
vector<int> cnt(字符域大小, 0);
for (int i = 0; i < A.size(); i++) {
cnt[索引(A[i])]++; // A:加
cnt[索引(B[i])]--; // B:减
}
for (int v : cnt) if (v != 0) return false;
return true;if (A.length() != B.length()) return false;
int[] cnt = new int[字符域大小];
for (int i = 0; i < A.length(); i++) {
cnt[索引(A.charAt(i))]++; // A:加
cnt[索引(B.charAt(i))]--; // B:减
}
for (int v : cnt) if (v != 0) return false;
return true;复杂度
时间复杂度
O(n)
两个字符串各扫一遍
空间复杂度
O(1)
只用 26 个计数槽
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 有效的字母异位词 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
如果包含 Unicode 怎么办?+
用哈希表计数,空间随不同字符数增长。
这道题为什么用「哈希计数」,换最直接的暴力解会差在哪?+
哈希计数抓住了本题的结构特征,把暴力解里重复的工作省掉;暴力解通常要多嵌套一层枚举,数据一大就超时。具体对比见上文「暴力解及其卡点」与「最优解逐步推演」两节。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。