一分钟吃透一道面试算法题——字母异位词分组(最优解)

一、前置知识(可跳过)

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 (包含字母异位词的数组) 转换为一个数组,并返回。

实例与展示

四、结语

再见!

相关推荐
Mr.H012710 分钟前
快速排序的常见构思
数据结构·算法
网络点点滴14 分钟前
watch监视-ref基本类型数据
前端·javascript·vue.js
mit6.82417 分钟前
背包dp|格雷码
算法
西洼工作室23 分钟前
前端接口安全与性能优化实战
前端·vue.js·安全·axios
大布布将军24 分钟前
《前端九阴真经》
前端·javascript·经验分享·程序人生·前端框架·1024程序员节
幸运小圣26 分钟前
for...of vs for 循环全面对比【前端JS】
开发语言·前端·javascript
rit843249927 分钟前
基于MATLAB的PCA+SVM人脸识别系统实现
人工智能·算法
RTC老炮28 分钟前
webrtc降噪-NoiseEstimator类源码分析与算法原理
算法·webrtc
用户95451568116243 分钟前
实际开发中 | 与 || 的使用方法及组件封装方案解析
前端
得帆云低代码44 分钟前
COC Asia 2025|得帆云 ETL:顺应 Hive 新特性,重塑数据管道的未来
前端