有效的字母异位词:26 格一加一减
多重集相等 ⇔ 每个字母的出现次数相等。s 里加一、t 里减一,26 格回到全零就是异位词。
长度不同直接假。开 [26]int,同步扫 s 和 t:s 的字母 +1,t 的字母 −1。结束时数组等于零值,则两串字符种类和数量完全一致。时间 O(n),空间 O(1)。字母表不是小写时改用哈希表。
这是 LeetCode 242. Valid Anagram。大白话:给两个字符串 s 和 t,判断它们是不是字母异位词——由相同的字母组成,每个字母出现次数也相同,只是顺序可以不同。
主例 s = "anagram",t = "nagaram",都是 a 三个、g、m、n、r 各一个,返回 true。对照 s = "rat",t = "car",字母对不上,返回 false。长度不同也直接假,例如 "ab" 和 "a"。
第一反应是两串都排序再比。正确,但排序构造了全序,题目只要计数。缺口是一张 26 格的差表:一边加一边减,看能不能抵消干净。
判定的是多重集,不是字典序
异位词 ⇔ 两个串作为字符多重集相等。先比长度:长度不同,多重集的基数不同,必假。长度相同再比每种字符的个数。主例长度都是 7,进入计数;rat 和 car 长度都是 3,也要靠计数才能否决。
本题常见约束是小写字母,计数表固定 26 格,额外空间 O(1)。若改成任意 Unicode,26 格不够,要用哈希表,空间随不同字符数增长。面试把这句前提说清楚。
排序后比较:对,但多做了全序
两串排序后相等,则多重集相等。主例 anagram 与 nagaram 都变成 aagmnr,返回 true。演示第一帧是原串,第二帧是排完的 aagmnr 对 aagmnr。rat 与 car 变成 art 与 acr,不相等。
时间 O(n log n)。排序要决定每个位置谁在前,题目并不需要这个顺序,只要个数。它能当对照,不是这道题逼出来的线性写法。
26 格:s 加一,t 减一,看是否回零
开一张 26 格的表。对每个下标 i,count[s[i]−'a']++,count[t[i]−'a']--。s 贡献正号,t 贡献负号。同一种字母在两边出现次数相同,正负抵消为 0;某一种多出来,对应格子会留下非零。扫完后整表等于零值数组,就是异位词。
用手走主例。演示第一帧表还空,准备从下标 0 加 a。加到下标 3,演示第二帧表上 a、n、g、r 各 1——此时只加了 s 的前几位,t 的减还没抵完。两边都扫完,a 的三次加与三次减抵消,其余字母同样回零。演示第三帧全是 0,op 标成 zero,返回 true。
对照 rat 与 car:r、a、t 各 +1,再 c、a、r 各 −1,留下 t=+1、c=−1,表不是全零,返回 false。长度不同的两串不要进这张表,先在门口挡掉,避免越界或误判。
抵消一加一减是差集为零。不必先统计完整的 s 再统计完整的 t,两个串可以同一下标一起处理,写出来更短。
字符集决定这张表怎么开
小写字母:var count [26]int,空间 O(1),还可以用数组相等一次性判断全零。Unicode:改 map[rune]int,最后再扫一遍看是否全零,空间 O(不同字符数)。
LC49 分组用的是同一张判定:排序键或 26 计数键。本题只比较两串,计数表够了,不必真的把键拼出来。
八行 Go:计数表
func isAnagram(s, t string) bool {if len(s) != len(t) { return false }var count [26]intfor i := 0; i < len(s); i++ {count[s[i]-'a']++count[t[i]-'a']--}return count == [26]int{}}
1长度不同直接假。主例都是 7,进循环;ab 与 a 不会去减越界。
2同一 i 上 s 加、t 减。主例七次之后 26 格全零。
3return count == [26]int{} 是整表比较。rat/car 会留下 t 和 c 的非零。
4前提是小写字母。字符集变了,这 26 格和 −'a' 一起失效。
总结
长度先挡;26 格 s 加 t 减,全零才是异位词。主例 anagram / nagaram 回零。
- 排序正确但 O(n log n),构造了题目不需要的顺序。
- 计数才是多重集判定。主例抵消为 0;rat/car 留下 t=+1、c=−1。
- 小写字母空间 O(1);Unicode 换哈希表。