[LeetCode刷题]49.字母异位词分组(通俗易懂的java题解)

大家好,今天我们来解决一道LeetCode上的经典题目——字母异位词分组。这道题难度中等,但其实是很多面试中的常客。我会用最通俗易懂的方式,一步步带你理解并解决这个问题。

题目描述

题目链接:49. 字母异位词分组 - 力扣(LeetCode)

给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。

示例 1:

输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]

输出: [["bat"],["nat","tan"],["ate","eat","tea"]]

解释:

  • 在 strs 中没有字符串可以通过重新排列来形成 "bat"
  • 字符串 "nat" 和 "tan" 是字母异位词,因为它们可以重新排列以形成彼此。
  • 字符串 "ate" ,"eat" 和 "tea" 是字母异位词,因为它们可以重新排列以形成彼此。

示例 2:

输入: strs = [""]

输出: [[""]]

示例 3:

输入: strs = ["a"]

输出: [["a"]]

提示:

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

先理解什么是字母异位词?

简单来说,字母异位词就是字母相同,但顺序不同的单词

比如:

  • "eat"、"tea"、"ate" 这三个单词,虽然字母顺序不同,但都是由 a、e、t 这三个字母组成的,所以它们是字母异位词
  • "tan" 和 "nat" 都是由 a、n、t 组成的,也是一组异位词
  • "bat" 由 a、b、t 组成,和其他单词都不相同,所以单独成一组

解题思路(核心思想)

这道题的关键是:如何判断两个单词是字母异位词?

有一个非常巧妙的方法:把单词的字母排序后作为这个单词的"特征"

为什么这样可行?

  • "eat" 排序后 → "aet"
  • "tea" 排序后 → "aet"
  • "ate" 排序后 → "aet"

看到没?只要是字母异位词,排序后的结果一定相同!这个排序后的字符串就像是一个"指纹",可以帮助我们识别所有异位词。

代码实现(详细注释版)

class Solution { public List<List<String>> groupAnagrams(String[] strs) { // 步骤1:创建一个"字典",用来存放分组 // key:单词的"特征"(比如排序后的字符串) // value:所有具有相同特征的单词 HashMap<String, List<String>> map = new HashMap<>(); // 步骤2:遍历每一个单词 for (int i = 0; i < strs.length; i++) { String currentWord = strs[i]; // 当前单词 // 步骤3:找出当前单词的特征 char[] letters = currentWord.toCharArray(); // 拆成字母 Arrays.sort(letters); // 字母排序 String feature = new String(letters); // 排序后就是特征 // 步骤4:看这个特征是否已经存在 if (map.containsKey(feature)) { // 如果存在,拿到这个组,把当前单词放进去 List<String> group = map.get(feature); group.add(currentWord); } else { // 如果不存在,创建新组 List<String> newGroup = new ArrayList<>(); newGroup.add(currentWord); map.put(feature, newGroup); } } // 步骤5:把字典里的所有组取出来返回 List<List<String>> result = new ArrayList<>(); for (List<String> group : map.values()) { result.add(group); } return result; } }

详细执行过程演示

让我们用题目中的例子,一步步看代码是如何执行的:

输入strs = ["eat", "tea", "tan", "ate", "nat", "bat"]

第1步:处理 "eat"

  • 当前单词:"eat"
  • 排序后得到特征:"aet"
  • map中还没有 "aet",所以创建新列表
  • map状态:{"aet" → ["eat"]}

第2步:处理 "tea"

  • 当前单词:"tea"
  • 排序后得到特征:"aet"
  • map中已有 "aet",获取列表 ["eat"],加入 "tea"
  • map状态:{"aet" → ["eat", "tea"]}

第3步:处理 "tan"

  • 当前单词:"tan"
  • 排序后得到特征:"ant"
  • map中没有 "ant",创建新列表
  • map状态:{"aet" → ["eat", "tea"], "ant" → ["tan"]}

第4步:处理 "ate"

  • 当前单词:"ate"
  • 排序后得到特征:"aet"
  • map中已有 "aet",获取列表 ["eat", "tea"],加入 "ate"
  • map状态:{"aet" → ["eat", "tea", "ate"], "ant" → ["tan"]}

第5步:处理 "nat"

  • 当前单词:"nat"
  • 排序后得到特征:"ant"
  • map中已有 "ant",获取列表 ["tan"],加入 "nat"
  • map状态:{"aet" → ["eat", "tea", "ate"], "ant" → ["tan", "nat"]}

第6步:处理 "bat"

  • 当前单词:"bat"
  • 排序后得到特征:"abt"
  • map中没有 "abt",创建新列表
  • map状态:{"aet" → ["eat", "tea", "ate"], "ant" → ["tan", "nat"], "abt" → ["bat"]}

最后:返回结果

  • 取出map中所有的value(即三个列表)
  • 返回:[["eat","tea","ate"], ["tan","nat"], ["bat"]]

复杂度分析

  • 时间复杂度:O(n × klogk)
    • n 是字符串数组的长度
    • k 是字符串的最大长度
    • 需要对每个字符串排序,排序的时间复杂度是 O(klogk)
  • 空间复杂度:O(n × k)
    • 需要存储所有的字符串

总结与思考

  1. 核心技巧:通过排序把异位词转换成相同的特征字符串
  2. 数据结构选择:HashMap非常适合这种需要"根据特征分组"的场景
  3. 解题步骤
    • 遍历每个单词
    • 排序得到特征
    • 根据特征分组存储
    • 返回所有分组

相关题目推荐

  • 有效的字母异位词
  • 找到字符串中所有字母异位词
  • 字符串的排列

最后的话

这道题看似复杂,但掌握了"排序后作为特征"这个技巧后,就会变得非常简单。希望通过这篇文章,你已经完全理解了字母异位词分组的解法。如果还有不明白的地方,欢迎在评论区留言讨论!

如果这篇文章对你有帮助,别忘了点赞、收藏、关注哦!
你的支持是我持续分享的动力!

Read more

C++测试与调试:确保代码质量与稳定性

C++测试与调试:确保代码质量与稳定性

C++测试与调试:确保代码质量与稳定性 一、学习目标与重点 本章将深入探讨C++测试与调试的核心知识,帮助你确保代码的质量与稳定性。通过学习,你将能够: 1. 理解测试与调试的基本概念,掌握测试方法和工具 2. 学会使用单元测试框架,如Google Test和Catch2 3. 理解集成测试的重要性,确保系统的功能正确性 4. 学会使用调试工具,如GDB和Visual Studio调试器 5. 培养测试与调试思维,设计高质量的代码 二、测试的基本概念 2.1 测试的分类 测试可以分为以下几类: * 单元测试:测试单个函数或类的功能 * 集成测试:测试多个模块的集成功能 * 系统测试:测试整个系统的功能 * 验收测试:测试系统是否满足用户需求 * 性能测试:测试系统的性能指标 2.2 测试原则 测试应该遵循以下原则: * 测试应该尽可能早地进行 * 测试应该覆盖所有可能的场景 * 测试应该是自动化的

By Ne0inhk
C++ 异常处理机制:异常捕获、自定义异常与实战应用

C++ 异常处理机制:异常捕获、自定义异常与实战应用

第34篇:C++ 异常处理机制:异常捕获、自定义异常与实战应用 一、学习目标与重点 * 掌握异常处理的核心概念(异常、抛出、捕获、处理)及基本语法 * 理解 try-catch-throw 语句的执行流程,能够正确捕获和处理标准异常 * 学会自定义异常类,满足实际开发中的个性化异常场景需求 * 掌握异常处理的最佳实践,规避常见错误(内存泄漏、异常安全问题) * 理解异常规格说明(C++11前)与 noexcept 关键字的使用场景 * 结合实战案例,提升代码的健壮性和容错能力 💡 核心重点:try-catch 捕获规则、自定义异常的继承设计、异常安全保障、实战场景中的异常处理策略 二、异常处理概述 2.1 什么是异常处理 异常处理是C++中处理程序运行时错误的机制,核心是“将错误检测与错误处理分离”——在程序出错的地方(如除以零、内存分配失败)“抛出”

By Ne0inhk
手写一个C++ TCP服务器实现自定义协议(顺便解决粘包问题)

手写一个C++ TCP服务器实现自定义协议(顺便解决粘包问题)

在之前的博客中,我们了解了关于UDP和TCP的网络编程,直观的感受了一下网络套接字是如何使用的,并且成功的完成了客户端与服务端的网络通信,但是其中还有一个小细节我们可能会忽略,就是UDP是基于数据报进行传输的,一下子就将所有我们要发送的信息传送给对方,但是我们的TCP可是基于字节流进行传输的,我们如何保证读取上来的数据,是一个完整的报文呢? 我们在进行TCP网络通信的时候,通过调用connec函数调用,使客户端可以和服务端保持链接之后,客户端将自己想要发送的数据通过write系统调用写进对应的socket函数调用给我们返回的文件描述符所对应的文件中。 现在有一个问题就是我们向文件中写入的时候,直接将其放入即可,但是想要往出拿的时候就有点困难了,想要往出拿的人如果不知道放的人是如何放的,就会造成一系列的错误,这就好比放数据时先放了一个整形,又放了一个浮点数,还放了一个字符串,然而拿的人按照字符串,整形,浮点数这样的方式进行获取,这就会导致数据不一致的现象,所以一旦我们要发送一些带有结构化的数据时,就必须再次制定——协议,这样才能满足我们想要返送一些结构化数据的需求。 TCP是传输控

By Ne0inhk