JavaScript 算法详解:Hash表在字母异位词分组的应用(小白也能看懂)
本文将从零基础开始,用最通俗的语言详细解释 JavaScript 中「字母异位词分组」算法,就像老师在课堂上讲解一样。
什么是「字母异位词分组」?
首先我们来看一个生活中的例子:
"listen"(听) 和"silent"(安静) 这两个单词,它们的字母都是一样的,只是顺序不同 → 这就是字母异位词"eat"和"tea"也是一样的道理
现在的问题是:给你一堆单词,把所有字母异位词分到一组。
比如给你:["eat", "tea", "tan", "ate", "nat", "bat"]
应该分成:
- 第一组:
["eat", "tea", "ate"](它们都是由 a,e,t 组成的) - 第二组:
["tan", "nat"](它们都是由 a,n,t 组成的) - 第三组:
["bat"](只有它一个)
JavaScript 代码详细解释
var groupAnagrams = function(strs) {
const m = new Map(); // 1. 创建一个"分类盒子"
for (const s of strs) { // 2. 挨个看每个单词
// 3. 把单词的字母重新排列(排序)
const sortedS = s.split('').sort().join('');
if (!m.has(sortedS)) { // 4. 检查有没有这种"字母组合"的盒子
m.set(sortedS, []); // 5. 如果没有,就新建一个空盒子
}
// 6. 把单词放进对应的盒子里
m.get(sortedS).push(s);
}
// 7. 把所有的盒子拿出来,组成一个大包裹返回
return Array.from(m.values());
};
一步步详细拆解
1. var groupAnagrams = function(strs) {
var groupAnagrams = function(strs) {
groupAnagrams是函数的名字(就像一个机器的名字叫"分词机")strs是输入参数(就像你把一堆单词扔进机器)function是告诉电脑:"我定义一个函数,名字叫groupAnagrams,它会接收一个叫strs的参数"
比如:
groupAnagrams(["eat", "tea"])→ 你把["eat", "tea"]传给函数
2. const m = new Map();
const m = new Map();
const:声明一个常量(不能被修改的变量),就像一个标签m:这是给这个东西起的名字,就像"分类柜"new Map():创建一个"哈希表"(可以想象成一个有很多抽屉的柜子)
Map 就像一个智能分类柜:
- 你可以给它一个"钥匙"(key),它就能给你对应的"物品"(value)
- 比如:
柜子.放东西("钥匙A", "苹果")→ 把苹果放在钥匙A对应的抽屉里柜子.拿东西("钥匙A")→ 拿到苹果
3. for (const s of strs) {
for (const s of strs) {
for:表示"循环"(重复做某件事)const s:每次循环时,用s来代表当前的单词of strs:在strs数组中一个一个地取单词
如果
strs = ["eat", "tea", "tan"]:
- 第1次循环:
s = "eat"- 第2次循环:
s = "tea"- 第3次循环:
s = "tan"
4. const sortedS = s.split('').sort().join('');
这是最神奇的一步!我们来拆开看:
4.1 s.split('')
"eat".split('') → ["e", "a", "t"]
split(''):把字符串拆成一个个字母组成的数组
4.2 .sort()
["e", "a", "t"].sort() → ["a", "e", "t"]
sort():把数组里的元素按字母顺序排列
4.3 .join('')
["a", "e", "t"].join('') → "aet"
join(''):把数组里的元素拼接成一个字符串
所以:
"eat"→"aet""tea"→"aet"← 它们排序后是一样的!"tan"→"ant"
5. if (!m.has(sortedS)) {
if (!m.has(sortedS)) {
if:表示"如果...那么..."!:表示"不是"、"不存在"m.has(sortedS):询问分类柜m:"你有没有叫sortedS这个钥匙的抽屉?"
如果
sortedS = "aet":
m.has("aet")→ 问:"有没有钥匙叫 'aet' 的抽屉?"!m.has("aet")→ "如果没有的话..."
6. m.set(sortedS, []);
m.set(sortedS, []);
m.set(key, value):往分类柜里放东西sortedS:钥匙(比如"aet")[]:一个空数组(就像一个空盒子)
m.set("aet", [])→ 创建一个钥匙叫 "aet" 的抽屉,里面放一个空盒子
7. m.get(sortedS).push(s);
m.get(sortedS).push(s);
m.get(sortedS):拿到钥匙sortedS对应的抽屉里的盒子.push(s):把单词s放进这个盒子
如果
sortedS = "aet",s = "eat":
m.get("aet")→ 拿到装着字母异位词的盒子.push("eat")→ 把 "eat" 放进去
8. return Array.from(m.values());
return Array.from(m.values());
m.values():拿出分类柜里所有抽屉里的东西(所有装着单词的盒子)Array.from():把这些东西变成一个数组return:把结果交给外面的人
实际运行过程演示
让我们用 ["eat", "tea", "tan"] 来追踪整个过程:
| 步骤 | s | sortedS | m 的状态 | 操作 |
|---|---|---|---|---|
| 1 | "eat" | "aet" | {} | m.set("aet", []) → {"aet": []} |
m.get("aet").push("eat") → {"aet": ["eat"]} | ||||
| 2 | "tea" | "aet" | {"aet": ["eat"]} | m.has("aet") 为 true,跳过 set |
m.get("aet").push("tea") → {"aet": ["eat", "tea"]} | ||||
| 3 | "tan" | "ant" | {"aet": ["eat", "tea"]} | m.has("ant") 为 false |
m.set("ant", []) → {"aet": ["eat", "tea"], "ant": []} | ||||
m.get("ant").push("tan") → {"aet": ["eat", "tea"], "ant": ["tan"]} |
最后 m.values() 得到:[["eat", "tea"], ["tan"]]
核心思想总结
"字母异位词"排序后完全一样,所以可以用排序后的字符串作为"分类标签"
就像:
"eat","tea","ate"都属于"aet"类别"tan","nat"都属于"ant"类别
JavaScript 语法小知识
| 语法 | 含义 | 例子 |
|---|---|---|
const | 声明常量 | const x = 5; |
new Map() | 创建哈希表 | const map = new Map(); |
for...of | 遍历数组 | for(const item of array) |
split() | 拆分字符串 | "abc".split('') → ["a", "b", "c"] |
sort() | 排序 | ["c", "a", "b"].sort() → ["a", "b", "c"] |
join() | 连接数组 | ["a", "b", "c"].join('') → "abc" |
push() | 添加元素 | arr.push(item) |
has() | 检查键是否存在 | map.has(key) |
set() | 设置键值对 | map.set(key, value) |
get() | 获取值 | map.get(key) |
总结
这个算法巧妙地利用了:
- 排序:让字母异位词变得"一样"
- 哈希表:快速分类存储
- 循环:逐个处理每个单词
最终实现了高效的字母异位词分组!
你还在学习 JavaScript 的路上吗?在评论区分享你遇到的困惑,我们一起讨论!
#javascript #算法 #前端开发 #面试准备 #编程入门