LeetCode hot 100 每日一题(2)--49. 字母异位词分组

leetcode hot 100 第二题
首先我们看看题目描述:

给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的所有字母得到的一个新单词。
示例 1:
输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [[“bat”],[“nat”,“tan”],[“ate”,“eat”,“tea”]]

示例 2:
输入: strs = [""]
输出: [[“”]]

示例 3:
输入: strs = ["a"]
输出: [[“a”]]

提示:

  • 1 <= strs.length <= 1 0 4 10^4 104
  • 0 <= strs[i].length <= 100
  • strs[i] 仅包含小写字母

字母异位词理解: 两个字符串互为字母异位词,当且仅当两个字符串包含的字母相同。简单点说,也就是字母异位词就是包含字母相同的那些词,tap、pat和apt就互为字母异位词。

解法1 排序

class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        // 定义一个哈希表 map 来存储按字母异位词分组的字符串
        // 键是字符串(每个字母异位词排序后的结果),
        // 值是字符串的列表(所有属于该字母异位词组的单词)。
        Map<String, List<String>> map = new HashMap<String, List<String>>();
        
        // 遍历所有输入的字符串
        for(String str : strs){
            // 将字符串转换为字符数组
            // 这个转换的目的是为了后续排序。
            char[] array = str.toCharArray();
            
            // 对字符数组进行排序,这样字母异位词排序后会得到相同的字符串
            Arrays.sort(array);
            
            // 将排序后的字符数组转换回字符串作为哈希表的键
            String key = new String(array);
            
            // 从哈希表中取出该键对应的列表,如果没有则创建一个新的列表
            List<String> list = map.getOrDefault(key, new ArrayList<String>());
            
            // 将当前的字符串添加到列表中
            list.add(str);
            
            // 将更新后的列表放回哈希表中
            map.put(key, list);
        }

        // 返回哈希表中所有的列表
        return new ArrayList<List<String>>(map.values());
    }
}

首先我们来理一理总体思路:

  1. 遍历输入字符串数组
    对每个字符串进行处理。

  2. 将字符串排序
    将字符串转换为字符数组,并对字符数组进行排序。排序后的结果对于互为字母异位词的字符串来说是相同的。

  3. 利用哈希表归类

    • 以排序后的字符串作为键。
    • 使用 HashMap<String, List<String>> 快速查找,判断该键是否已存在。
    • 如果存在,则将原字符串加入对应的列表;如果不存在,则创建一个新的列表并添加字符串。
  4. 整理输出结果
    将哈希表中所有的字符串列表(即各组字母异位词)转换为一个 List<List<String>> 并返回。

那么观察这道题目,其实我们发现这一道题目中对于数据类型的选择非常重要。刚好借这一道题目复习java中常见的数据类型,具体可见我的另一篇博客:

常见的 Java 数据类型和常用方法

结合本题「字母异位词分组」来判断数据类型和选用方法,可以从以下几个方面考虑:

  • 输入数据类型

    • 题目给定的是一个字符串数组(String[] strs)。因此,最初的数据类型是数组。数组适合存储固定数量的数据,但在题目中需要动态分组,所以接下来需要将数据分组到集合中。
  • 分组要求

    • 题目要求将所有字母异位词归为一组。由于每组的元素数量不固定,因此需要使用动态数组,比如 List。而最终返回的结果是一个包含多个字符串列表的集合,即 List<List<String>>
  • 如何快速查找和归类

    • 当需要根据某种规则(这里是将字符串排序后的结果)来将数据归类时,哈希表Map)是很好的选择。
    • 具体来说,可以用 HashMap<String, List<String>>
      • 键(Key):排序后的字符串,用来表示该组的“标准形式”。
      • 值(Value):一个 List<String>,存放所有与该键对应的原字符串(即字母异位词组)。
    • 使用 HashMap 可以在遍历每个字符串时,快速判断排序后的字符串是否已经作为键存在,从而决定是新建一个列表还是向已有列表中添加字符串。
  • 操作数据的灵活性

    • ArrayList
      • 动态数组 ArrayList 实现了 List 接口,可以方便地添加和遍历元素,适用于存放分组后的字符串列表。
      • 最终需要将 HashMap 中的所有值转换成 List<List<String>>,因此选择 ArrayList 作为返回类型。

总结一下就是:

  • 输入:题目提供的是 String[](数组)。
  • 分组存储:为了根据排序后的字符串快速归类,选用 HashMap<String, List<String>>
  • 分组内的数据:每组的字符串数量不定,选用 ArrayList<String> 来存储每组的字符串。
  • 最终返回:题目要求返回的结果是一个列表的列表,因此选用 List<List<String>>,可以通过 new ArrayList<>(map.values()) 得到。

问题与解答

下面我们借助一些问题来巩固一下这道题目!

[NOTE] 问题1
问题1:Map<String, List<String>> map = new HashMap<String, List<String>>();这句话中List<String>是什么用法?是字符串列表吗?

  • 解释
    List<String> 是 Java 泛型(Generics)的一种用法,表示一个只能存放字符串(String)的列表。泛型帮助你在编译时检查数据类型,防止放入错误类型的元素。List<String> 表示一个字符串列表,它只能存储字符串。
  • 常见使用场景
  1. 存储数据集合:当你需要按顺序存放多个字符串时,可以使用 List<String>。例如存储用户输入的一组单词。
  2. 分组数据:在本题中,我们需要将所有互为字母异位词的字符串归为一组,所以每个键对应一个字符串列表。
  • 示例说明
// 定义一个只存储字符串的列表
List<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Cherry");
System.out.println(fruits);  // 输出:[Apple, Banana, Cherry]

[NOTE] 问题2
问题2:for(String str : strs)这样的循环和for(int i = 0;i < nums.length; i++)这种常见的有什么区别?
解释

  • for(String str : strs)增强型 for 循环(for-each 循环),适用于遍历数组或集合中的每个元素,代码更简洁且没有下标操作。
  • for(int i = 0; i < nums.length; i++) 是传统的 for 循环,需要使用下标访问数组元素,适用于需要知道元素索引或在遍历过程中修改下标的场景。
  • 举例说明
String[] words = {"hello", "world"};
// 增强 for 循环
for (String word : words) {
   System.out.println(word);
}

// 传统 for 循环
for (int i = 0; i < words.length; i++) {
  System.out.println(words[i]);
}

两种方式都可以遍历数组,但 for-each 写法更加简洁且避免了索引越界的风险。

[NOTE] 问题3
问题3:Arrays.sort(array);这句的用法是java自带的吗?对字符数组排序是怎么做到的?转换为ASCII码吗?

  • Arrays.sort(array); 是 Java 自带的工具方法,位于 java.util.Arrays 类中。
  • 当对字符数组排序时,底层会使用一种高效的排序算法(如双轴快速排序)对字符的 Unicode 值进行比较。
  • 对于英文小写字母,它们的 Unicode 值与 ASCII 码是一致的,所以排序时会按照字母的自然顺序(例如 a, b, c, …)排序。
  • 示例说明
char[] letters = {'c', 'a', 'b'};
Arrays.sort(letters);
System.out.println(new String(letters)); // 输出:"abc"

[NOTE] 问题4
问题4:String key = new String(array);为什么将排序后的字符数组转换回字符串,用作哈希表中的键之后,所有字母异位词将共享同一个键?
解释

  • 将字符数组排序后,相同字母构成的不同单词(即字母异位词)会得到相同的排序结果。例如,“eat”、“tea”、“ate” 排序后都得到 "aet"
  • 用排序后的字符串作为键,就可以把这些异位词归为同一组,因为它们具有相同的键。
  • 示例说明
String word1 = "eat";
String word2 = "tea";
// 将两个单词转换为字符数组并排序
char[] arr1 = word1.toCharArray();
char[] arr2 = word2.toCharArray();
Arrays.sort(arr1);
Arrays.sort(arr2);
String key1 = new String(arr1); // "aet"
String key2 = new String(arr2); // "aet"
// 因为 key1 和 key2 相同,所以可以用相同的键存储在 map 中

[NOTE] 问题5
问题5:List<String> list = map.getOrDefault(key, new ArrayList<String>());这句中new ArrayList<String>()在java中是如何使用的?为什么选择这样的数据类型?
解释

  • new ArrayList<String>() 表示创建一个新的空的字符串列表。
  • map.getOrDefault(key, new ArrayList<String>()) 中,如果 map中不存在 key对应的值,就返回一个新的空 ArrayList。
  • 选择 ArrayList 是因为它实现了 List接口,具备动态调整大小、方便添加元素的特点,非常适合存储多个字符串。
  • 示例说明
// 假设 map 中还没有 key 为 "aet" 的条目
List<String> list = map.getOrDefault("aet", new ArrayList<String>());
list.add("eat");  // 将 "eat" 加入列表中
map.put("aet", list);

[NOTE] 问题6
问题6:return new ArrayList<List<String>>(map.values());这句中new ArrayList<String>()在java中是如何使用的?为什么选择这样的数据类型?
解释

  • map.values() 返回的是一个 Collection<List<String>>,包含了 map 中所有的值(每个值是一个字符串列表)。
  • 使用 new ArrayList<List<String>>(map.values()) 将这些值转换为一个新的 ArrayList。
  • 选择 ArrayList 是因为返回类型要求是 List<List>,ArrayList 是 List 接口的常用实现,便于后续遍历和使用。
  • 示例说明
// map.values() 返回所有分组的列表
Collection<List<String>> groups = map.values();
// 将 Collection 转换成 ArrayList,以便返回
List<List<String>> result = new ArrayList<>(groups);
return result;

总结回顾

ArrayList

在这道题目中出现了ArrayList,借机回顾一下用法:
ArrayList 是一个动态数组,适用于需要频繁添加、删除和随机访问元素的场景。它提供了简单易用的 API,使得我们可以方便地操作集合数据。由于底层基于数组实现,访问速度快,但在中间插入或删除元素时可能需要移动数据,因此在设计程序时需要考虑操作的频率和数据量。

  • 声明与初始化

    • 声明一个 ArrayList 时,通常使用泛型指定存储的数据类型。例如:
      ArrayList<String> list = new ArrayList<>();

    • 也可以使用接口类型声明:
      List<String> list = new ArrayList<>();

  • 添加元素

    • 在列表末尾添加元素:list.add("Apple");

    • 在指定位置插入元素:list.add(0, "Banana");

  • 访问元素

    • 根据下标获取元素:String fruit = list.get(0);

    • 获取列表的大小: int size = list.size();

  • 修改元素

    • 替换指定位置的元素:list.set(1, "Cherry");
  • 删除元素

    • 根据下标删除元素:list.remove(0);

    • 删除指定对象:list.remove("Cherry");

  • 遍历列表

    • 使用增强型 for 循环:

      for (String item : list) {
           System.out.println(item); 
           }
      
    • 使用传统 for 循环:

      for (int i = 0; i < list.size(); i++) {    
       System.out.println(list.get(i)); 
       }
      
  • 常用其他方法

    • 判断列表是否为空:list.isEmpty();

    • 清空列表:list.clear();

    • 检查是否包含某个元素:list.contains("Apple");

    • 将列表转换为数组:String[] array = list.toArray(new String[0]);

### LeetCode Hot 100 Problems 列表 LeetCode 的热门题目列表通常由社区投票选出,涵盖了各种难度级别的经典编程挑战。这些题目对于准备技术面试非常有帮助。以下是部分 LeetCode 热门 100 题目列表: #### 数组与字符串 1. **两数之和 (Two Sum)** 2. **三数之和 (3Sum)** 3. **无重复字符的最长子串 (Longest Substring Without Repeating Characters)** 4. **寻找两个正序数组的中位数 (Median of Two Sorted Arrays)** #### 动态规划 5. **爬楼梯 (Climbing Stairs)** 6. **不同的二叉搜索树 (Unique Binary Search Trees)** 7. **最大子序列和 (Maximum Subarray)** #### 字符串处理 8. **有效的括号 (Valid Parentheses)** 9. **最小覆盖子串 (Minimum Window Substring)** 10. **字母异位词分组 (Group Anagrams)** #### 图论 11. **岛屿数量 (Number of Islands)** 12. **课程表 II (Course Schedule II)** #### 排序与查找 13. **最接近原点的 K 个点 (K Closest Points to Origin)** 14. **接雨水 (Trapping Rain Water)** 15. **最长连续序列 (Longest Consecutive Sequence)[^2]** #### 堆栈与队列 16. **每日温度 (Daily Temperatures)** 17. **滑动窗口最大值 (Sliding Window Maximum)** #### 树结构 18. **验证二叉搜索树 (Validate Binary Search Tree)** 19. **二叉树的最大路径和 (Binary Tree Maximum Path Sum)** 20. **从前序与中序遍历序列构造二叉树 (Construct Binary Tree from Preorder and Inorder Traversal)** #### 并查集 21. **冗余连接 II (Redundant Connection II)** #### 贪心算法 22. **跳跃游戏 (Jump Game)** 23. **分割等和子集 (Partition Equal Subset Sum)** #### 双指针技巧 24. **环形链表 II (Linked List Cycle II)[^1]** 25. **相交链表 (Intersection of Two Linked Lists)** #### 其他重要题目 26. **LRU缓存机制 (LRU Cache)** 27. **打家劫舍系列 (House Robber I & II)** 28. **编辑距离 (Edit Distance)** 29. **单词拆分 (Word Break)** 此列表并非官方发布版本而是基于社区反馈整理而成。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值