一、前置知识(可跳过)
1.质数分解的唯一性
算术基本定理:任何大于1的自然数,要么本身就是一个质数,要么可以唯一地分解成一系列质数的乘积。
在字母异位词分组的场景中, 我们将每个字母映射到一个唯一的质数 ,然后将字符串中所有字母对应的质数相乘。 由于质数分解的唯一性, 确保了只有字母异位词(它们所包含的字符完全相同,只是顺序不同)才会有相同的质数乘积。 这使得我们可以使用这个质数乘积作为键来将字母异位词分组。 这是为什么这种方法可以有效地将字母异位词分组的关键原因。
二、题目描述
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的所有字母得到的一个新单词。
示例 1:
less
输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]
示例 2:
lua
输入: strs = [""]
输出: [[""]]
示例 3:
lua
输入: strs = ["a"]
输出: [["a"]]
提示:
1 <= strs.length <= 104
0 <= strs[i].length <= 100
strs[i]
仅包含小写字母
三、题解
js
var groupAnagrams = function(strs) {
const anagramGroups = new Map();
for (const str of strs) {
// Optimized Key Generation: Prime Number Product
let key = 1;
for (let i = 0; i < str.length; i++) {
const charCode = str.charCodeAt(i) - 'a'.charCodeAt(0);
// Prime numbers mapped to characters (a=2, b=3, c=5, etc.)
const prime = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101][charCode];
key *= prime;
}
// Empty string case:
if (str.length === 0) key = 0; //Crucial for handling empty strings
if (anagramGroups.has(key)) {
anagramGroups.get(key).push(str);
} else {
anagramGroups.set(key, [str]);
}
}
return Array.from(anagramGroups.values());
};
核心思想
字母异位词包含相同的字符,只是字符的顺序不同。如果我们能将每个字母异位词映射到一个唯一的"指纹" (或者说键),那么我们就可以用这个键来将它们分组。 这里使用的关键在于将每个字母映射到一个唯一的质数,然后将字符串中所有字母对应的质数相乘,得到的乘积就作为这个字符串的键。 由于质数分解的唯一性,确保了只有字母异位词才会有相同的键。
详细解析
ini
/**
* @param {string[]} strs
* @return {string[][]}
*/
var groupAnagrams = function(strs) {
const anagramGroups = new Map();
- 这部分是标准的函数定义和初始化。
anagramGroups
是一个Map
对象,用于存储最终的结果。Map
对象的 key 是字符串对应的质数乘积,value 是包含相同字母异位词的数组。
rust
for (const str of strs) {
- 遍历输入的字符串数组
strs
。
csharp
// Optimized Key Generation: Prime Number Product
let key = 1;
- 初始化
key
为 1。注意,这里必须从 1 开始,因为任何数乘以 1 还是它本身。key
将用于存储当前字符串str
的质数乘积。
ini
for (let i = 0; i < str.length; i++) {
const charCode = str.charCodeAt(i) - 'a'.charCodeAt(0);
// Prime numbers mapped to characters (a=2, b=3, c=5, etc.)
const prime = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101][charCode];
key *= prime;
}
-
这个循环遍历当前字符串
str
中的每个字符。const charCode = str.charCodeAt(i) - 'a'.charCodeAt(0);
:计算字符的 ASCII 码, 并减去 'a' 的 ASCII 码,得到字符在字母表中的索引(0-25)。 例如,'a' 的charCode
是 0, 'b' 的charCode
是 1,以此类推。const prime = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101][charCode];
: 使用预定义的质数数组,将每一个字母映射到一个唯一的质数。 'a' 映射到 2, 'b' 映射到 3, 'c' 映射到 5,以此类推。key *= prime;
: 将当前字符对应的质数乘以key
。 这样,key
就逐步累积了字符串中所有字符对应的质数的乘积。
ini
// Empty string case:
if (str.length === 0) key = 0; //Crucial for handling empty strings
-
关键步骤: 处理空字符串的情况。 如果字符串为空 (
str.length === 0
), 则将key
设置为 0。 这样做很重要,原因如下:- 如果不特殊处理,空字符串的
key
仍然是 1(因为初始值是 1,并且没有乘以任何质数)。 这会导致所有空字符串都被错误地分组到一起,因为它们都具有相同的key
(即 1)。 - 将空字符串的
key
设置为 0,确保了空字符串与其他任何非空字符串都能正确区分开。
- 如果不特殊处理,空字符串的
vbnet
if (anagramGroups.has(key)) {
anagramGroups.get(key).push(str);
} else {
anagramGroups.set(key, [str]);
}
-
这部分与之前的代码类似,用于将具有相同
key
的字符串分组。if (anagramGroups.has(key))
:检查Map
中是否已经存在具有相同key
(相同质数乘积)的条目。anagramGroups.get(key).push(str);
:如果存在, 则将当前字符串str
添加到该key
对应的数组中。else { anagramGroups.set(key, [str]); }
: 如果不存在, 则创建一个新的key
,并将当前字符串str
作为一个新的数组添加到Map
中。
csharp
}
return Array.from(anagramGroups.values());
};
- 循环结束后,
anagramGroups
包含了所有分组好的字母异位词。Array.from(anagramGroups.values())
将Map
中的 values (包含字母异位词的数组) 转换为一个数组,并返回。
实例与展示


四、结语
再见!