
本文深入解析 isAnagram 函数的时间复杂度为何常被表述为 O(n + m),而非直观的 O(n);阐明当两字符串长度相等时 O(n + m) 与 O(n) 的等价性,并对比多种实现方式的性能本质与代码优雅性。
本文深入解析 `isanagram` 函数的时间复杂度为何常被表述为 o(n + m),而非直观的 o(n);阐明当两字符串长度相等时 o(n + m) 与 o(n) 的等价性,并对比多种实现方式的性能本质与代码优雅性。
在分析变位词判定算法(如 LeetCode 242)的时间复杂度时,一个常见困惑是:为什么标准解答常标注为 O(n + m),而不是更简洁的 O(n)?关键在于——复杂度分析必须基于输入规模的最一般化表达,而非隐含前提。
考虑如下经典实现:
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
if len(s) != len(t):
return False
count = [0] * 26
for i in range(len(s)):
count[ord(s[i]) - ord('a')] += 1
count[ord(t[i]) - ord('a')] -= 1
for val in count:
if val != 0:
return False
return True此处 s 长度为 n,t 长度为 m。虽然题设常要求判断“是否互为变位词”,隐含 n == m,但算法本身并未强制这一约束——len(s) != len(t) 的检查发生在循环之前,属于 O(1) 操作(Python 中 len() 是 O(1)),而主循环遍历的是 s(或 t)的长度。严格来说,第一轮 for 循环执行 min(n, m) 次并不安全(因需同时访问 s[i] 和 t[i]),所以实际逻辑依赖 n == m 成立后才进入循环。因此,该解法隐含假设输入满足 n == m,此时循环执行 n 次,后续检查固定大小数组 count(26 个元素)为 O(1)。于是总时间复杂度为:
-
O(1)(长度比较) -
+ O(n)(主循环,因n == m) -
+ O(1)(遍历 26 元素数组)
→ 合计 O(n)
那为何文献中常写 O(n + m)?这是更严谨、更通用的表达方式:
- 若放宽题设(例如设计一个可处理任意两字符串、返回字符频次差的通用函数),则需分别遍历
s(O(n))和t(O(m))来建频次表; - 即使本题中
n == m,O(n + m) = O(2n) = O(n)在渐进意义下完全等价; - 使用
O(n + m)可清晰体现算法对两个独立输入规模的依赖,避免误读为“仅依赖单个变量”。
✅ 正确结论:O(n) 与 O(n + m) 在本题中数学等价,但 O(n + m) 是更规范、可扩展的复杂度描述。
值得一提的是,代码还可进一步优化以提升可读性与 Pythonic 风格,而不影响渐进复杂度:
-
替换最后的显式循环:
return not any(count) # O(1) —— 因 count 长度恒为 26
-
利用
collections.Counter实现语义化一行解:from collections import Counter class Solution: def isAnagram(self, s: str, t: str) -> bool: return len(s) == len(t) and Counter(s) == Counter(t)⚠️ 注意:
Counter(s) == Counter(t)的时间复杂度仍是 O(n + m),因为需先构建两个哈希表(各 O(n)、O(m)),再逐项比较键值对(最坏 O(n + m))。虽底层常数略高,但大 O 级别不变,且代码意图一目了然。
? 总结:
- 时间复杂度标注应优先反映输入参数的显式依赖关系,
O(n + m)比O(n)更准确、更通用; - 当
n == m时,二者等价,不必纠结“哪个更小”; - 工程实践中,在保持 O(n + m) 复杂度的前提下,应优先选择
Counter方案——它更健壮(支持任意 Unicode 字符)、更简洁、更少出错,符合 Python 哲学。

















