题目描述
思路解析动画文字版
不够! 比如 pattern=ab、s=dog dog:只看「字母→单词」,a→dog、b→dog 都没矛盾,会误判成 true。但 dog 同时被 a 和 b 占了,违反一一对应。所以必须两个方向都记。
也能做:把相同字母的下标聚成组、相同单词的下标聚成组,再看两套分组是否一模一样。但要建两套分组结构、再逐组比对,写起来啰嗦。其实一遍扫就够了。
核心:一边扫一边记两份对应。c2w 管「字母指向哪个单词」,w2c 管「单词指向哪个字母」。两张表互相把关,任何一边和历史记录打架就立刻 false。
对每一对字母-单词 (c, w):① c 之前记过且记的不是 w → 矛盾 false;② w 之前记过且记的不是 c → 矛盾 false;都过关就把 c→w、w→c 各登记一次。扫完没翻车就是 true。
先对齐两排:把 s 按空格切成单词表 [dog, cat, cat, dog],正好 4 个,和 pattern 的 4 个字母一一对齐(格子下方小数字是下标)。下面会逐对处理,并在底下长出 c2w / w2c 两张哈希表。
长度先过一关:第一道关:字母个数必须等于单词个数。这里都是 4,过关(全亮)。如果一句话单词比字母多或少,根本对不上,直接 false。
目标先剧透:提前剧透结果:扫完后字母 a(下标0、3)全程对 dog、b(下标1、2)全程对 cat,反过来也成立,没有任何冲突。下面看两张表怎么一对一对建起来。
对0 · (a, dog) · 查 c2w:第一对:字母 a(下标0) 配单词 dog。先查正向表 c2w:里头没有 a,说明 a 还没被绑定,先放行。
对0 · (a, dog) · 查 w2c:再查反向表 w2c(切到单词→字母这张):dog 也没被任何字母占用。两个方向都查过、都是空的,这一对可以放心登记。
对0 · (a, dog) · 双向登记:都没冲突,两个方向各登记一次:c2w 加「a → dog」(绿色高亮),w2c 同步记「dog → a」。下标0 锁定,a 和 dog 绑死。
对1 · (b, cat) · 查 c2w:第二对:字母 b(下标1) 配 cat。查正向 c2w,只有 a→dog 那行,没有 b,b 还是新的。
对1 · (b, cat) · 查 w2c:切到反向表 w2c:现在它有 dog→a 一行,查 cat 没有,说明 cat 还没被占用。两边都查空,这对可登记。
对1 · (b, cat) · 双向登记:登记第二组:c2w 新增「b → cat」(高亮),w2c 同步记「cat → b」。下标0、1 都锁定,a、b 各有归属。
对2 · (b, cat) · 查 c2w · 命中:第三对又是 b(下标2) 配 cat。查正向 c2w:b 已经在(蓝色高亮),记的是 cat,正好等于当前单词——正向一致。
对2 · (b, cat) · 查 w2c · 命中:再查反向 w2c:cat 这行(高亮)记的是 b,也正好等于当前字母。两个方向都对得上,记录已存在且一致,无需新增。
对2 · 复查通过 · 表不变:因为 b、cat 早绑好了,这对只是复查通过,两张表不动,下标2 确认对齐(已点亮 0、1、2)。这正是「同字母必须对同单词」。
对3 · (a, dog) · 查 c2w · 命中:最后一对 a(下标3) 配 dog。查正向 c2w:a 那行(高亮)记的是 dog,等于当前单词——正向一致。
对3 · (a, dog) · 查 w2c · 命中:查反向 w2c:dog 这行(高亮)记的是 a,等于当前字母。两方向都对得上,最后一对也复查通过。
扫描完成 · 全程无矛盾:四对都没翻车(下标 0、1、2、3 全亮),两张表最终是 a↔dog、b↔cat 的干净双向映射。返回 true,和示例答案完全对上。
把最后一个单词换成 fish 看看会怎样。前三对和刚才完全一样(a→dog, b→cat, b→cat 复查通过),问题出在最后一对。
反例 · 前两对照旧绑定:反例 s="dog cat cat fish":头两对 (a,dog)、(b,cat) 跟正例一模一样,两张表照常建起 a→dog、b→cat(下标0、1 点亮)。问题还没暴露。
反例 · 对2 (b, cat) 复查通过:第三对还是 (b, cat),和正例一样复查通过(下标2 点亮)。三对都没事——坑在最后一对,因为最后一个单词被换成了 fish。
反例 · 对3 · (a, fish) · 冲突!:最后一对是 a 配 fish。查 c2w 发现 a 早就绑死 dog(高亮行),可现在要它对 fish——同一个字母不能对两个不同单词,直接返回 false。这就是正向表把关的作用。
前面那个 fish 冲突,单靠正向表就能抓到。但有一种情况只有反向表才拦得住——开头提的 ab/dog dog。下面演给你看。
反例2 · 对0 · (a, dog) 登记:pattern=ab、s="dog dog"。第一对 (a, dog) 正常登记:c2w 记 a→dog,反向 w2c 记下 dog→a(关键就在这条反向记录)。
反例2 · 对1 · 查 w2c · 拦截!:第二对 (b, dog):正向查 c2w,b 是新的(只看正向会放过!)。但切到反向表 w2c 一查——dog 这行(高亮)早记了 a,dog 已被 a 占用,b 抢不走 → false。这就是必须有反向表的原因。
一一对应是双向约束:字母不能对多个单词(正向表管),单词也不能被多个字母占(反向表管)。只记一张会漏掉「不同字母指向同一单词」这种(如 ab→dog dog),所以两张缺一不可。
雷区实演 · 漏建反向表:如果只建正向 c2w:a→dog、b→dog 各自都没冲突,程序高高兴兴返回 true。可 dog 被两个字母占了,根本不是一一对应。补上反向表 w2c,第二对就会因 dog 已属 a 而正确返回 false。
边界三连:注意单词之间是空格分隔,切词后个数要和字母数严格相等。重复字母/重复单词都靠两张表的「已存在必须一致」自然处理,无需特判。
面试追问:把「双向一一映射 = 两张表互相把关」讲透,并点出和 LC205 同构,是这题的核心得分点。再补一句空格切分的工程细节会更稳。
参考代码
class Solution: def wordPattern(self, pattern, s): words = s.split() if len(pattern) != len(words): return False c2w, w2c = {}, {} # 两张哈希表 for c, w in zip(pattern, words): if c in c2w and c2w[c] != w: return False if w in w2c and w2c[w] != c: return False c2w[c] = w w2c[w] = c return True复杂度
- 时间复杂度:O(n),n 为 pattern 长度(=单词数),每对做常数次哈希查与写
- 空间复杂度:O(n),两张哈希表最多各存 n 条映射
易错点
面试追问把动画讲成自己的话
追问和 LC205 同构字符串有什么关系?
追问只用一张表,能不能靠某种技巧搞定?
追问如果单词里可能有多个连续空格怎么办?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
两个数组的交集
LeetCode 349 · 简单 · 沿着 哈希套路 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题