【LeetCode 热题 100】49. 字母异位词分组

Problem: 49. 字母异位词分组

文章目录

整体思路

这段代码旨在解决一个经典的字符串问题:字母异位词分组 (Group Anagrams) 。问题要求将一个字符串数组中的所有字符串,按照它们是否互为字母异位词(Anagrams)来进行分组。字母异位词是指由相同的字符以不同的顺序构成的字符串(例如,"eat" 和 "tea")。

该算法采用了一种非常经典且高效的策略:使用排序后的字符串作为"规范哈希键"。其核心逻辑步骤如下:

  1. 核心思想:异位词的"身份证"

    • 算法的基石在于一个关键的观察:所有互为字母异位词的字符串,当它们内部的字符被排序后,都会得到完全相同的字符串。
    • 例如,"eat", "tea", "ate" 这三个异位词,排序后都变成了 "aet"。
    • 因此,这个排序后的字符串 "aet" 就可以作为这一组异位词的唯一标识,或者说"身份证"。
  2. 数据结构选择

    • 算法选择了一个 哈希表 (HashMap) ,Map<String, List<String>>,来存储分组结果。
    • 键 (Key) :哈希表的键是排序后的"规范字符串"(如 "aet")。
    • 值 (Value) :哈希表的值是一个字符串列表,用来存放所有共享同一个"规范字符串"的原始字符串(如 ["eat", "tea", "ate"])。
  3. 算法步骤

    • 遍历输入数组 :算法遍历输入的字符串数组 strs 中的每一个字符串 s。
    • 生成哈希键 :对于每个字符串 s:
      a. 将其转换为字符数组 char[] sortedS = s.toCharArray();。
      b. 对这个字符数组进行排序 Arrays.sort(sortedS);。
      c. 将排序后的字符数组转换回字符串 new String(sortedS),这就是该字符串的"规范哈希键"。
    • 分组 :
      a. 使用这个"规范哈希键"去哈希表中查找。
      b. m.computeIfAbsent(key, _ -> new ArrayList<>()) 是一个非常简洁的Java 8+的写法。它的作用是:如果 key 不存在,就创建一个新的 ArrayList 并与 key 关联;如果 key 已存在,就直接返回与 key 关联的现有 ArrayList。
      c. .add(s):无论上面是新建还是获取,最终都会将原始字符串 s 添加到对应的列表中。
    • 返回结果 :遍历结束后,哈希表 m 的 values() 集合中就包含了所有分组好的列表。将 m.values() 转换为一个新的 ArrayList 并返回。

完整代码

java 复制代码
import java.util.*;

class Solution {
    /**
     * 将字符串数组按照字母异位词进行分组。
     * @param strs 输入的字符串数组
     * @return 分组后的字符串列表
     */
    public List<List<String>> groupAnagrams(String[] strs) {
        // 创建一个哈希表,键是排序后的规范字符串,值是属于该组的原始字符串列表。
        Map<String, List<String>> m = new HashMap<>();
        
        // 遍历输入的每一个字符串
        for (String s : strs) {
            // 步骤 1: 生成"规范哈希键"
            // 将当前字符串转换为字符数组
            char[] sortedS = s.toCharArray();
            // 对字符数组进行排序
            Arrays.sort(sortedS);
            // 将排序后的字符数组转换回字符串,作为哈希表的键
            String key = new String(sortedS);
            
            // 步骤 2: 将原始字符串放入对应的分组
            // computeIfAbsent(key, k -> new ArrayList<>()) 的语义:
            // - 如果 map 中不存在 key,则执行 lambda 表达式创建一个 new ArrayList<>(),
            //   并将其作为 value 与 key 关联,然后返回这个新创建的 list。
            // - 如果 map 中已存在 key,则直接返回与 key 关联的现有 list。
            // 接着,.add(s) 将原始字符串 s 加入到获取到的 list 中。
            m.computeIfAbsent(key, _ -> new ArrayList<>()).add(s);
        }
        
        // 步骤 3: 返回结果
        // m.values() 返回哈希表中所有值的集合(即所有分组的 List<String>)。
        // 将这个集合转换为一个新的 ArrayList 并返回。
        return new ArrayList<>(m.values());
    }
}

时空复杂度

时间复杂度:O(N * K log K)
  1. 变量定义 :
    • N:输入数组 strs 中字符串的数量。
    • K:数组中字符串的最大长度。
  2. 外层循环 :for (String s : strs) 循环执行 N 次。
  3. 循环内部操作 :这是分析的关键。
    • s.toCharArray():将长度为 k 的字符串转为字符数组,时间复杂度为 O(k)。
    • Arrays.sort(sortedS):对一个长度为 k 的字符数组进行排序,时间复杂度为 O(k log k)。
    • new String(sortedS):将长度为 k 的字符数组转为字符串,时间复杂度为 O(k)。
    • m.computeIfAbsent(...) 和 add(...):哈希表的插入和获取操作,平均时间复杂度是 O(k) ,因为哈希键(字符串)的比较和哈希码计算的成本与字符串长度 k 成正比。

综合分析 :

每次循环的成本由其中最耗时的操作决定,即排序操作 O(k log k) 。

由于这个操作在外层循环中重复了 N 次,所以总的时间复杂度是 O(N * K log K)。

空间复杂度:O(N * K)

  1. 主要存储开销 :哈希表 m 是主要的额外空间消耗。
  2. 空间大小 :
    • 在最坏的情况下,如果所有字符串都互不为异位词,那么哈希表中将有 N 个条目。
    • 每个条目的键 是一个排序后的字符串,其长度最多为 K。
    • 每个条目的值 是一个列表,其中包含原始字符串,其长度也最多为 K。
    • 因此,哈希表需要存储所有 N 个字符串(以原始形式或排序形式)。所有字符串的总长度可以表示为 N * K_avg(K_avg为平均长度),在最坏情况下为 O(N * K)。
  3. 临时变量 :在循环中,char[] sortedS 需要 O(K) 的临时空间。但这部分空间在每次循环后都会被回收,并且其最大值 O(K) 不会超过哈希表的总空间 O(N*K)。

综合分析 :

算法所需的额外空间主要由哈希表决定,它存储了所有原始字符串的信息。因此,空间复杂度为 O(N * K)。

参考灵神

相关推荐
Discipline10292 小时前
尼泊尔河流流量:天气驱动的预测与高流量风险
人工智能·算法
XuCoder2 小时前
改一个数,右边全得重算,这题怎么扛住两万次查询
算法
小羊没烦恼!2 小时前
在Scrum中实施敏捷建模
java·开发语言·windows·算法·c#
weixin_307779132 小时前
基于睿擎工业开发平台的预训练视觉模型轻量化适配与低代码部署优化
开发语言·算法
hold?fish:palm3 小时前
45 二叉树的右视图
开发语言·c++·算法
粤鼎恒业3 小时前
惠州工厂电子料回收厂家推荐:从交接单反推筛选标准
大数据·数据库·算法·硬件架构·硬件工程·pcb工艺·材料工程
小爷毛毛(卓寿杰)4 小时前
【Agent 意图识别】输出协议、评估与置信度
人工智能·算法·大模型·prompt·大语言模型·agent
优质酸酸乳4 小时前
力扣100题之21合并两个有序链表
算法·leetcode·链表
旖旎夜光4 小时前
LeetCode 137 : 只出现一次的数字 II(位运算) —— 题解
c++·笔记·学习·算法·leetcode·力控
旋生万物4 小时前
Agent System Prompt 注入螺旋公理:让 AI 推理不跑偏的可复制模板(附完整 Prompt)
人工智能·算法