跳到主要内容

49. 字母异位词分组

题目描述​

给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。

字母异位词 是由重新排列源单词的所有字母得到的一个新单词。

示例​

示例 1:

输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]

示例 2:

输入: strs = [""]
输出: [[""]]

示例 3:

输入: strs = ["a"]
输出: [["a"]]

解题思路​

第一步:理解问题本质​

字母异位词的特点是:排序后的字符串相同。

例如:

  • "eat", "tea", "ate" 排序后都是 "aet"
  • "tan", "nat" 排序后都是 "ant"

第二步:暴力解法​

思路:对每个字符串,检查其他字符串是否是它的异位词。

class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
n = len(strs)
# used[j] 标记第 j 个串已经归过组,避免重复处理
used = [False] * n
result = []

for i in range(n):
# 已归组的串不再当新组的起点
if used[i]:
continue
group = [strs[i]]
used[i] = True
# 只和后面的串比,前面的必然已经处理过
for j in range(i + 1, n):
# 排序后相同即互为异位词;弊端是每个 j 都要重排一次 strs[i]
if not used[j] and sorted(strs[i]) == sorted(strs[j]):
group.append(strs[j])
used[j] = True
result.append(group)

return result

缺点:时间复杂度 O(n² × k log k),效率太低。

第三步:最优解法 —— 哈希表​

核心洞察:

  • 异位词排序后的结果相同,可以作为 key
  • 使用哈希表将相同 key 的字符串分到同一组

算法步骤:

  1. 创建一个空哈希表
  2. 遍历每个字符串:
    • 将字符串排序作为 key
    • 将原字符串加入 key 对应的列表
  3. 返回哈希表的所有值

为什么正确:

  • 异位词排序后必然相同,所以会被分到同一组
  • 非异位词排序后不同,会被分到不同组

完整代码实现​

算法可视化 · 逐帧回放这道题的代码4 帧 · 录制输入 ["eat", "tea", "tan", "ate", "nat", "bat"]
from typing import List

class Solution:
"""
字母异位词分组 - 哈希表

核心思想:
字母异位词(anagram)的特点是:排序后的字符串相同。
例如 "eat", "tea", "ate" 排序后都是 "aet"。

算法步骤:
1. 遍历每个字符串,将其排序作为 key
2. 将原字符串加入 key 对应的列表
3. 返回所有列表

为什么用排序作为 key?
因为异位词的字母组成完全相同,排序后必然相同。

时间复杂度:O(n * k log k),n 是字符串数量,k 是最大字符串长度
空间复杂度:O(n * k)
"""

def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
anagrams = {}
for s in strs:
key = ''.join(sorted(s)) # 排序作为 key
if key not in anagrams:
anagrams[key] = []
anagrams[key].append(s)
return list(anagrams.values())

示例推演​

以 strs = ["eat","tea","tan","ate","nat","bat"] 为例:

字符串排序后哈希表状态
"eat""aet"{"aet": ["eat"]}
"tea""aet"{"aet": ["eat", "tea"]}
"tan""ant"{"aet": ["eat", "tea"], "ant": ["tan"]}
"ate""aet"{"aet": ["eat", "tea", "ate"], "ant": ["tan"]}
"nat""ant"{"aet": [...], "ant": ["tan", "nat"]}
"bat""abt"{"aet": [...], "ant": [...], "abt": ["bat"]}

结果:[ ["eat","tea","ate"], ["tan","nat"], ["bat"] ]


复杂度分析​

解法时间复杂度空间复杂度说明
暴力O(n² × k log k)O(n × k)两两比较
哈希表(最优)O(n × k log k)O(n × k)n是字符串数量,k是最大长度

易错点总结​

1. key 的生成​

# join 拼出不可变字符串,哈希值稳定,才可以当字典的 key
key = ''.join(sorted(s)) # 正确
key = sorted(s) # 错误,返回的是列表,不能作为字典 key

2. 空字符串的处理​

空字符串排序后仍是空字符串,可以正常处理。

3. 使用 defaultdict 简化代码​

from collections import defaultdict

# defaultdict(list) 让缺失的 key 自动建出空列表,省掉"先判断再初始化"
anagrams = defaultdict(list)
for s in strs:
# 排序结果相同的串必然互为异位词,天然落在同一个 key 上
anagrams[''.join(sorted(s))].append(s)
return list(anagrams.values())

扩展思考​

1. 如果不使用排序?​

可以用字符计数作为 key。例如 "eat" 的计数是 a:1, e:1, t:1,可以表示为字符串 "1#1#1#0#..." 或元组 (1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0)。

时间复杂度可以优化到 O(n × k)。

2. 如果需要返回每组内排序后的结果?​

可以在返回前对每个组进行排序。


相关题目​

自测​

加载评论中...