简单题往往是夯实基础、突破进阶的关键------LeetCode 242题「有效的字母异位词」,作为一道标注为「简单」的字符串经典题,不仅是面试高频送分题,更是理解哈希表应用、掌握时空复杂度优化的入门范本。本文将从题目解读入手,逐行解析给定解法的核心逻辑,深挖时空优化的关键思路,帮你吃透这道题的本质,做到"会写、会优化、会举一反三"。
一、题目核心解读
题目描述:给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的 字母异位词。
核心定义:字母异位词,指由相同字母按照不同顺序组成的字符串。例如 "anagram" 和 "nagaram" 是异位词,"rat" 和 "car" 则不是。
关键隐含条件(优化突破口):
-
若两个字符串长度不相等,无需后续判断,直接可确定不是字母异位词;
-
题目默认字符串仅包含字母(无需处理数字、符号、空格等特殊字符),这是后续空间优化的核心前提。
看似简单的题干,实则藏着优化的关键------很多人能写出正确解法,但未必能做到时空复杂度最优,而这正是这道题的核心考察点:基础哈希表应用 + 边界条件优化。
二、给定解法解析(基础哈希表版)
先看我们手头的给定解法,这是哈希表(Map)的经典应用,逻辑清晰、正确性达标,我们逐行拆解,理解其底层逻辑和时空复杂度。
typescript
function isAnagram(s: string, t: string): boolean {
let map = new Map();
// 第一步:统计字符串s中每个字符的出现次数
for (let i = 0; i < s.length; i++) {
let char = s[i];
if (map.has(char)) {
map.set(char, map.get(char) + 1);
} else {
map.set(char, 1);
}
}
// 第二步:用字符串t抵消map中的字符计数
for (let i = 0; i < t.length; i++) {
let char = t[i];
// 若t中出现s中没有的字符,直接返回false
if (map.has(char)) {
map.set(char, map.get(char) - 1);
} else {
return false;
}
}
// 第三步:检查所有字符的计数是否为0(即s和t的字符完全匹配)
for (let value of map.values()) {
if (value !== 0) {
return false;
}
}
return true;
};
2.1 解法逻辑拆解(三步核心)
核心思路:利用哈希表的"键值对"特性,统计字符出现次数,通过「计数→抵消→校验」三步,判断两个字符串的字符组成是否完全一致。
-
计数阶段:遍历字符串s,将每个字符作为key存入Map,字符出现的次数作为value(首次出现设为1,重复出现则累加计数);
-
抵消阶段:遍历字符串t,每遇到一个字符,就将Map中对应key的value减1(若t中出现s中没有的字符,直接返回false,提前终止判断);
-
校验阶段:遍历Map的所有value,若存在非0值,说明s和t的字符出现次数不匹配,返回false;全部为0则说明字符组成完全一致,返回true。
2.2 初始时空复杂度分析
这是优化的基础,必须先吃透初始解法的时空开销,才能找到优化方向:
-
时间复杂度:O(n),其中n是字符串s(或t)的长度。整个解法包含三次线性遍历(遍历s、遍历t、遍历Map),无嵌套循环,遍历次数与字符串长度成正比,属于线性时间复杂度,是时间层面的较优解;
-
空间复杂度:O(1)(易误解为O(n))。很多人会认为Map的空间随字符串长度变化,但题目默认字符串仅包含26个小写英文字母,因此Map的key最多只有26个,与字符串长度n无关,属于"常数级空间",空间开销固定。
小结:给定解法的时空复杂度已达标,但仍有优化空间------核心优化方向的是「减少无效执行」和「降低空间开销」,让代码更高效、更简洁。
三、核心优化:时空双维度升级(重点)
初始解法虽好,但在实际刷题(追求极致效率)和工程应用(代码简洁、轻量化)中,仍可进一步优化。结合题目隐含条件,我们从两个核心方向入手,优化后不改变核心逻辑,却能显著提升执行效率、精简代码。
优化1:提前判断长度,减少无效执行(最实用、性价比最高)
核心优化点:利用"长度不相等则非异位词"的隐含条件,在函数开头增加一行长度判断,提前终止无效的遍历操作------尤其在字符串较长、长度不匹配的场景下,能节省大量时间(可过滤80%以上的无效场景)。
同时,简化计数逻辑,用一行代码替代if-else判断,让代码更简洁,优化后完整代码如下:
typescript
function isAnagram(s: string, t: string): boolean {
// 提前判断长度,直接终止无效执行(核心优化点1)
if (s.length !== t.length) return false;
let map = new Map();
// 简化计数逻辑,替代if-else(核心优化点2)
for (let i = 0; i < s.length; i++) {
let char = s[i];
map.set(char, (map.get(char) || 0) + 1);
}
for (let i = 0; i < t.length; i++) {
let char = t[i];
if (!map.has(char)) return false;
map.set(char, map.get(char) - 1);
// 新增:计数小于0直接返回,提前发现不匹配(核心优化点3)
if (map.get(char) < 0) return false;
}
// 无需再遍历Map:长度已相等,且计数未出现负数,必为异位词
return true;
}
优化效果说明:
-
时间优化:O(1)时间的长度判断,提前过滤无效场景;t遍历阶段新增计数校验,避免后续Map遍历,进一步节省时间;
-
代码优化:简化计数逻辑,减少冗余代码,可读性和简洁度提升,更符合刷题规范。
优化2:用数组替代Map,进一步降低空间开销(极致空间优化)
核心优化点:结合"字符串仅包含26个小写英文字母"的前提,用一个长度固定为26的数组,替代Map存储字符出现次数------数组的索引访问速度比Map的get/set方法更快,空间开销也更小(常数级空间的极致优化)。
核心原理:将字母a-z对应数组索引0-25,通过字符的ASCII码计算对应索引(char.charCodeAt(0) - 'a'.charCodeAt(0)),实现字符与索引的映射,优化后完整代码如下:
typescript
function isAnagram(s: string, t: string): boolean {
if (s.length !== t.length) return false;
// 用长度为26的数组替代Map,索引对应a-z(核心优化)
const arr = new Array(26).fill(0);
// 统计s中字符出现次数
for (const char of s) {
arr[char.charCodeAt(0) - 'a'.charCodeAt(0)]++;
}
// 抵消t中字符,同时校验
for (const char of t) {
const index = char.charCodeAt(0) - 'a'.charCodeAt(0);
arr[index]--;
// 计数小于0,说明t中该字符过多,直接返回false
if (arr[index] < 0) return false;
}
return true;
}
优化效果说明(时空双提升):
-
空间优化:数组长度固定为26,比Map的空间开销更小(无Map对象的额外内存占用),属于极致的常数级空间;
-
时间优化:数组的索引访问是O(1)操作,比Map的get/set方法执行速度更快,尤其在字符串较长时,效率提升明显;
-
代码优化:逻辑更简洁,无需处理Map的has/get/set等方法,代码量进一步减少,刷题时更易书写、不易出错。
四、优化总结与对比(一目了然)
为了清晰看到优化效果,我们将三种解法(初始解法、优化1、优化2)的时空复杂度、核心特点进行对比,方便刷题时根据场景选择:
| 解法类型 | 时间复杂度 | 空间复杂度 | 核心特点 |
|---|---|---|---|
| 初始解法(Map) | O(n) | O(1) | 逻辑清晰,易理解,适合入门 |
| 优化1(Map+提前判断) | O(n)(实际更快) | O(1) | 代码简洁,减少无效执行,性价比最高 |
| 优化2(数组替代Map) | O(n)(最快) | O(1)(最优) | 时空极致优化,适合追求高效、刷题进阶 |
五、刷题小贴士(避坑+举一反三)
这道题虽然简单,但刷题时容易踩坑,也容易忽略其背后的核心价值,分享两个关键小贴士:
-
避坑点:不要忘记"长度判断"------很多人写完代码后,测试用例能通过,但遇到长度不相等的场景时,会做无效遍历,导致效率偏低;同时,不要误以为空间复杂度是O(n),记住"26个字母是固定常数",属于O(1)空间;
-
举一反三:这道题的核心是"哈希计数"思想,这种思想可迁移到其他类似题目中,比如「383. 赎金信」「49. 字母异位词分组」,掌握这道题的优化思路,能轻松解决这类"字符计数、匹配"类题目。
六、总结
LeetCode 242题「有效的字母异位词」,作为一道简单题,其价值不在于"写出正确解法",而在于"吃透哈希计数思想,掌握时空优化技巧"。
从初始的Map解法,到"提前判断长度"的简洁优化,再到"数组替代Map"的极致优化,每一步都是基于题目隐含条件的合理推导------刷题的本质,就是不断挖掘题干信息,用更优的方式解决问题。
对于这道题,刷题时优先掌握"优化1"(性价比最高),进阶时掌握"优化2"(极致时空),理解两种优化的核心逻辑,就能轻松应对面试和后续的同类题目。