49. 字母异位词分组

中等 含交互动画

学完这道你应该能: 说清它为什么用「哈希分组」,而不是死记步骤; 合上页面,自己默写出核心代码; 用 30 秒把思路讲清楚。

AI 私教 · 就着本题动画讲
卡住了不用硬扛,按 8 步把这题真正走通。
从读题、试解、找法到手写代码,边问边打勾,适合第一次系统学算法的同学。

题目描述

字母异位词(含相同字母、只是顺序不同的词)分到同一组,返回所有分组。

strs = ["eat","tea","tan","ate","nat","bat"]
输出 = [[eat,tea,ate], [tan,nat], [bat]] 共 3 组

思路解析

最直接的想法:拿每个词去和已有的每一组比,判断是不是异位词。可这样每比一对都要排序一次,n 个词两两比是 O(n²) 级,重复排序太多。

转折:异位词排序后完全一样,所以把「排序后的字符串」当 key。建哈希表 key → 词列表,遍历时算出 key 就往对应桶里塞,同 key 自动相遇,不用两两比较,只扫一遍。

开始扫描 · 哈希表为空:准备一个空哈希表 mp(key → 词列表)。从第一个词 eat 开始,依次给每个词算 key、归桶。

处理 "eat" · key 不存在 → 新建桶:"eat" 排序后 = "aet"。哈希表里还没有这个 key,新建一个桶,放入 eat。这是「新 key」分支。

处理 "tea" · key 已存在 → 归入:"tea" 排序也是 "aet",key 已存在,直接塞进同一个桶——这就是「已存在 key」分支,和上一步对比着看。

处理 "tan" · 新 key → 新建第 2 桶:"tan" 排序 = "ant",是个从没见过的 key,新建第二个桶放入 tan。

处理 "ate" · 回到第 1 桶:"ate" 排序 = "aet",又命中第一个桶,归入。注意它和 eat、tea 隔了两个词才出现,但靠统一 key 照样聚到一起。

处理 "nat" · 归入第 2 桶:"nat" 排序 = "ant",key 已存在,进第二个桶,和 tan 作伴。

处理 "bat" · 新 key → 新建第 3 桶:"bat" 排序 = "abt",新 key,新建第三个桶。注意它只比 ant 多了字母 b,排序后就完全不同,所以不会和 tan/nat 混。

扫描完毕 · 取出所有桶:六个词只扫了一遍,每个词算一次 key 就归位。把所有桶的列表取出来就是答案:[[eat,tea,ate], [tan,nat], [bat]] 共 3 组

分组 / 去重这类题,核心就是想出一个「同类元素算出来一样」的归一化 key,剩下交给哈希表自动聚合。

参考代码(Python)

Python
1mp = collections.defaultdict(list)
2for s in strs:
3    key = class="cl-str">"".join(sorted(s))          # 排序后的串当 key
4    mp[key].append(s)                 # 塞进对应桶(不存在自动建)
5return list(mp.values())

复杂度分析

  • 时间复杂度:O(n·k·logk) —— n 个词,每个词排序需 k·logk(k 为词长)
  • 空间复杂度:O(n·k) —— 哈希表存下全部 n 个词,每个长 k

套路模板

记住骨架:想一个「同类→同 key」的归一化函数,用 defaultdict 自动建桶。key 两种写法:排序(简单)或 26 字母计数元组(更快);其它分组题只换 key 算法。

Python
1# 「把同类元素分组 / 去重」都套这个骨架
2from collections import defaultdict
3groups = defaultdict(list)
4for s in strs:
5    key = class="cl-str">"".join(sorted(s))            # 法1: 排序当 key  O(klogk)
6    # cnt=[0]*26; for c in s: cnt[ord(c)-97]+=1; key=tuple(cnt)  # 法2: 字母计数 O(k) 更快
7    groups[key].append(s)               # 塞进同一个桶
8return list(groups.values())

易错点

  • 错误写法:直接拿原字符串 s 当 key正确写法:用 sorted(s) 或 26 字母计数元组当 key(异位词原串本就不同(eat≠tea),必须归一化后才会算出同一个 key)
  • 错误写法:用 [0]*26 这个 list 直接当 dict 的 key正确写法:用 tuple(cnt) 元组当 key(list 不可哈希,当 key 会 TypeError;元组才能哈希)
  • 错误写法:普通 dict 不判存在就 mp[key].append正确写法:用 defaultdict(list) 或先判 key 再建(普通 dict 里 key 不存在时直接 append 会 KeyError)

以上文字与上方动画完全同一思路:先看动画建立直觉 → 读文字巩固 → 关掉页面自己默写一遍代码,能写出来才算真的掌握

下一题 →167. 两数之和 II ← 返回题库