博客:从「字母异位词分组」中学到的 Python 哈希与字符串处理核心技巧
题号:LeetCode #49 Group Anagrams
语言:Python
核心收获:如何设计"键(key)"来分组数据 + 深入理解
sorted、defaultdict、ord、tuple等关键工具
一、题目回顾
给定一个字符串数组 strs,将所有字母异位词(Anagram)组合在一起。
- 字母异位词 :由相同字母以不同顺序组成的单词(如
"eat"、"tea"、"ate") - 要求:返回一个列表,每个子列表包含一组异位词
示例:
python
输入: ["eat","tea","tan","ate","nat","bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]
二、解题核心思想:用"标准化签名"作为分组依据
要判断两个字符串是否是异位词,关键是看它们包含的字母种类和数量是否完全一致。
于是我们引入一个核心策略:
为每个字符串生成一个唯一的"签名(signature)",只要签名相同,就是异位词。
然后用哈希表(字典) 把相同签名的字符串归为一组。
接下来,我通过这道题学会了三种生成签名的方法,每一种都带来了新的 Python 技巧。
三、方法一:排序法 ------ 学会 ''.join(sorted(s))
✅ 核心代码:
python
key = ''.join(sorted(s))
🔍 我学到的关键点:
-
sorted(s)对字符串排序后返回的是字符列表pythonsorted("bac") # → ['a', 'b', 'c'](不是 "abc"!)因为
sorted()函数对任何可迭代对象操作后统一返回list,这是 Python 的设计规范。 -
要用
''.join(...)把字符列表拼回字符串python''.join(['a','b','c']) # → "abc"这是字符串拼接的高效方式 (比
+快得多)。 -
这个字符串可作为字典的 key
因为字符串是不可变类型,天然可哈希。
💡 应用场景:
- 所有需要"忽略顺序、只看组成"的问题(如异位词、变位数等)
四、方法二:使用 defaultdict ------ 告别手动初始化!
✅ 核心代码:
python
from collections import defaultdict
groups = defaultdict(list)
groups[key].append(s) # 即使 key 不存在,也会自动创建空列表!
🔍 我学到的关键点:
-
传统写法很啰嗦:
pythonif key not in groups: groups[key] = [] groups[key].append(s) -
defaultdict(list)自动处理"第一次出现"的情况- 当访问一个不存在的 key 时,它会自动调用
list()创建一个空列表 - 然后你可以直接
.append(),无需判断
- 当访问一个不存在的 key 时,它会自动调用
-
defaultdict是dict的子类,用法几乎一样最后仍可通过
list(groups.values())获取结果
💡 扩展知识:
defaultdict(int):用于计数(自动初始化为 0)defaultdict(set):用于去重集合- 这是 LeetCode 分组类题目的标准模板
五、方法三:字符频次法 ------ 学会 ord 与 tuple 作 key
✅ 核心代码:
python
count = [0] * 26 # 初始化长度为 26 的全零列表
for char in s:
count[ord(char) - ord('a')] += 1
key = tuple(count) # 转为元组才能当 dict 的 key
🔍 我学到的关键点:
1. [0] * 26 快速创建固定长度列表
- 这是初始化计数数组的惯用写法
- 比
for i in range(26): count.append(0)更简洁高效
2. ord() 函数获取字符的 ASCII 码
-
ord('a')→ 97 -
ord('b')→ 98 -
所以
ord(char) - ord('a')可将小写字母映射到 0~25python'a' → 0, 'b' → 1, ..., 'z' → 25
3. 为什么不用 if 判断?------ 数组下标天然支持"分类"
- 以前我总想:"如果遇到 'a' 就加到 a 的计数,遇到 'b' 就加到 b......"
- 但其实不需要 if !直接用
count[index] += 1即可 - 这是用空间换逻辑简化的经典思想
4. tuple(count) 让列表变成可哈希的 key
-
字典的 key 必须是不可变类型
-
list是可变的 → 不能当 key -
tuple是不可变的 → 可以当 keypythond = {} d[[1,2]] = 1 # ❌ 报错 d[(1,2)] = 1 # ✅ 成功
💡 优势 vs 排序法:
- 时间复杂度更低:O(M) vs O(M log M)(M 是字符串长度)
- 但仅适用于小写字母等有限字符集
六、完整代码对比
✅ 方法一:排序法(推荐,简洁通用)
python
from collections import defaultdict
from typing import List
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
groups = defaultdict(list)
for s in strs:
key = ''.join(sorted(s))
groups[key].append(s)
return list(groups.values())
✅ 方法三:频次法(高效,适合面试展示深度)
python
from collections import defaultdict
from typing import List
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
groups = defaultdict(list)
for s in strs:
count = [0] * 26
for char in s:
count[ord(char) - ord('a')] += 1
groups[tuple(count)].append(s)
return list(groups.values())
七、本题带给我的核心认知升级
| 以前的我 | 现在的我 |
|---|---|
想手写排序或用一堆 if 判断字符 |
知道用 sorted + join 或 ord + 数组计数 |
| 手动检查 key 是否存在再初始化 | 用 defaultdict 自动处理 |
| 不知道 list 不能当 dict 的 key | 理解"可哈希"概念,会用 tuple 转换 |
| 对字符串操作不熟 | 掌握 sorted(s) 返回列表、''.join() 拼接 |
八、延伸思考
-
如果字符串包含大写字母、数字、符号怎么办?
- 排序法依然适用 ✅
- 频次法需扩展数组大小或改用
dict计数
-
能否不用哈希表?
- 可以,但时间复杂度会退化到 O(N²M),不实用