LeetCode 242. 有效的字母异位词:解法解析与时空优化全攻略

简单题往往是夯实基础、突破进阶的关键------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 解法逻辑拆解(三步核心)

核心思路:利用哈希表的"键值对"特性,统计字符出现次数,通过「计数→抵消→校验」三步,判断两个字符串的字符组成是否完全一致。

  1. 计数阶段:遍历字符串s,将每个字符作为key存入Map,字符出现的次数作为value(首次出现设为1,重复出现则累加计数);

  2. 抵消阶段:遍历字符串t,每遇到一个字符,就将Map中对应key的value减1(若t中出现s中没有的字符,直接返回false,提前终止判断);

  3. 校验阶段:遍历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"(极致时空),理解两种优化的核心逻辑,就能轻松应对面试和后续的同类题目。

相关推荐
执着2592 小时前
力扣hot100 - 104、二叉树的最大深度
算法·leetcode·职场和发展
_OP_CHEN2 小时前
【算法基础篇】(五十四)解析错排问题:从信封错位到编程实战,一次性搞懂排列组合中的 “反常识” 难题!
算法·蓝桥杯·c/c++·组合计数·算法竞赛·acm/icpc·错排问题
David凉宸2 小时前
Vue 3生态系统深度解析与最佳实践
前端·javascript·vue.js
苦藤新鸡2 小时前
54 子集
算法·leetcode·动态规划
近津薪荼2 小时前
递归专题5——快速幂
c++·学习·算法
小龙报2 小时前
【数据结构与算法】指针美学与链表思维:单链表核心操作全实现与深度精讲
c语言·开发语言·数据结构·c++·物联网·算法·链表
全栈小52 小时前
【前端】win11操作系统安装完最新版本的NodeJs运行npm install报错,提示在此系统上禁止运行脚本
前端·npm·node.js
一起养小猫2 小时前
Flutter for OpenHarmony 实战:扫雷游戏算法深度解析与优化
算法·flutter·游戏
晚霞的不甘2 小时前
Flutter for OpenHarmony3D DNA 螺旋可视化:用 Canvas 构建沉浸式分子模型
前端·数据库·经验分享·flutter·3d·前端框架