LeetCode 第 49 题 Group Anagrams(字母异位词分组)

33 阅读3分钟
avascript
编辑
var groupAnagrams = function(strs) {
    const map = new Map();
    for (let i = 0; i < strs.length; i++) {
        let key = strs[i].split('').sort().join('');
        if (map.has(key)) {
            map.get(key).push(strs[i]);
        } else {
            map.set(key, [strs[i]]);
        }
    }
    return Array.from(map.values());
};

其中最关键的一步是:

js
编辑
let key = strs[i].split('').sort().join('');

以及最后的:

js
编辑
return Array.from(map.values());

本文将逐层拆解这行“魔法代码”背后的 API 原理,并解释 map.values() 到底返回了什么。


一、split(''):字符串 → 字符数组

作用

将字符串按指定分隔符拆分为数组。当分隔符为 ''(空字符串)时,会逐字符拆分

语法

js
编辑
str.split(separator)

示例

js
编辑
"eat".split('')   // → ["e", "a", "t"]
"hello".split('') // → ["h", "e", "l", "l", "o"]
"".split('')      // → []

注意事项

  • separator 可以是字符串或正则表达式;
  • 拆分不会修改原字符串(字符串不可变);
  • 空字符串拆分会得到空数组。

为什么用 ''
因为我们需要把单词打散成单个字母,才能进行后续排序。


二、.sort():对字符数组排序(原地修改)

作用

对数组元素进行排序,默认按字符串的 Unicode 编码升序排列。

语法

js
编辑
arr.sort([compareFunction])

示例

js
编辑
["e", "a", "t"].sort() // → ["a", "e", "t"]
["z", "b", "a"].sort() // → ["a", "b", "z"]

关键细节

  • 默认排序规则:将所有元素转为字符串后比较 Unicode 值;
  • 原地修改:直接改变原数组,不返回新数组;
  • 对于字母异位词(anagram),如 "eat" 和 "tea",排序后结果完全一致。

对比陷阱(数字排序)

js
编辑
[10, 2, 3].sort()           // → [10, 2, 3] ❌(按字符串 "10" < "2")
[10, 2, 3].sort((a,b)=>a-b) // → [2, 3, 10]

幸运的是,在本题中我们只处理字母,无需担心数字问题。


三、.join(''):字符数组 → 排序后的字符串

作用

将数组元素拼接成字符串,使用指定分隔符连接。

语法

js
编辑
arr.join(separator)

示例

js
编辑
["a", "e", "t"].join('')   // → "aet"
["a", "b", "c"].join('-')  // → "a-b-c"
[].join('')                // → ""

注意事项

  • 默认分隔符是 ,
  • 传入 '' 表示无间隔拼接
  • 返回新字符串,不修改原数组。

四、完整流程演示(以 "eat" 为例)

js
编辑
"eat"
  .split('')   // → ["e", "a", "t"]
  .sort()      // → ["a", "e", "t"](原数组被修改)
  .join('')    // → "aet"

🔑 核心思想
所有字母异位词经过此流程后,会生成相同的 key
例如:

  • "eat" → "aet"
  • "tea" → "aet"
  • "ate" → "aet"

因此,我们可以用这个 key 作为 Map 的键,实现自动分组。


五、map.values():获取所有分组结果

返回值类型

Map.prototype.values() 返回一个 Map Iterator(迭代器对象) ,包含 Map 中所有 value(按插入顺序)。

示例

js
编辑
const map = new Map();
map.set("aet", ["eat", "tea"]);
map.set("abt", ["bat"]);

const iter = map.values();
console.log(iter); // MapIterator { ["eat", "tea"], ["bat"] }

如何转为数组?

由于迭代器不能直接用于返回(LeetCode 要求数组),需转换:

js
编辑
// 方法1:Array.from()
Array.from(map.values())

// 方法2:扩展运算符
[...map.values()]

两者效果相同,最终返回:

js
编辑
[ ["eat", "tea"], ["bat"] ]

迭代器特性

特性说明
一次性遍历完后无法再次使用(需重新调用 .values()
有序严格按插入顺序返回
不实时同步遍历时修改 Map,迭代器内容可能不更新

六、总结要点 ✅

步骤API作用是否修改原数据
1split('')字符串 → 字符数组
2sort()字符数组排序是(原地)
3join('')数组 → 排序字符串(key)
4map.values()获取所有分组否(返回迭代器)
5Array.from()迭代器 → 数组

💡 为什么这个组合如此经典?
它利用“排序归一化”思想,将语义相同但顺序不同的数据映射到同一标识符,是哈希分组的典型范式。