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

一、前置知识(可跳过)

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

实例与展示

四、结语

再见!

相关推荐
健康胡1 分钟前
仿射变换 与 透视变换
图像处理·人工智能·深度学习·opencv·算法·机器学习·计算机视觉
L_cl4 分钟前
【Python 算法零基础 2.模拟 ④ 基于矩阵】
python·算法·矩阵
2301_807611497 分钟前
310. 最小高度树
c++·算法·leetcode·深度优先·回溯
浩~~13 分钟前
HTML5 浮动(Float)详解
前端·html·html5
@ chen19 分钟前
常见排序算法及其java实现
java·算法·排序算法
AI大模型顾潇1 小时前
[特殊字符] 本地大模型编程实战(29):用大语言模型LLM查询图数据库NEO4J(2)
前端·数据库·人工智能·语言模型·自然语言处理·prompt·neo4j
学习中的码虫1 小时前
数据结构中的高级排序算法
数据结构·算法·排序算法
山北雨夜漫步1 小时前
机器学习 Day17 朴素贝叶斯算法-----概率论知识
人工智能·算法·机器学习
九月TTS1 小时前
TTS-Web-Vue系列:Vue3实现内嵌iframe文档显示功能
前端·javascript·vue.js
爱编程的小学究1 小时前
【node】如何把包发布到npm上
前端·npm·node.js