LeetCode 49 字母异位词分组

点此看全部题解 LeetCode必刷100题:一份来自面试官的算法地图(题解持续更新中)

生活中的算法

你有没有玩过扑克牌?打完一局之后,我们通常会把散落的牌收集起来,按照花色分组整理。虽然每张牌的花色图案位置可能不同,但只要是同一个花色,我们就把它们放在一起。

这和我们今天要讲的"字母异位词分组"问题很像。字母异位词就像是同一花色的扑克牌,它们由相同的字母组成,只是字母的排列顺序不同。

问题描述

LeetCode第49题"字母异位词分组"是这样描述的:给你一个字符串数组,请你将所有的字母异位词放在同一个组里。字母异位词是由相同的字母重新排列组成的单词。

例如,“eat”、"ate"和"tea"就是字母异位词,因为它们都由字母’a’、‘e’、't’组成,只是排列顺序不同。

最直观的解法:排序比较法

如何判断两个单词是否是字母异位词?最直观的方法就是:把两个单词的字母都排序后比较,如果排序后相同,那就是字母异位词。

就像整理扑克牌时,我们会把每张牌的花色符号位置摆正,这样就容易看出它们是不是同一花色。

具体步骤是这样的:

  1. 对于每个单词,将其字符排序得到一个标准形式
  2. 使用这个标准形式作为key,原单词作为value,存入哈希表
  3. 具有相同key的单词就是字母异位词,将它们分到同一组

让我们用一个例子来模拟这个过程:

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

处理"eat":
- 排序后是"aet"
- 哈希表:{"aet": ["eat"]} 处理"tea":
- 排序后是"aet"
- 找到已有的组,添加进去
- 哈希表:{"aet": ["eat","tea"]} 处理"tan":
- 排序后是"ant"
- 创建新组
- 哈希表:{"aet": ["eat","tea"], "ant": ["tan"]} ...以此类推

这种思路可以用Java代码这样实现:

public List<List<String>> groupAnagrams(String[] strs) {
// 创建哈希表,key是排序后的字符串,value是原字符串列表
Map<String, List<String>> map = new HashMap<>(); for (String str : strs) {
// 将字符串转换为字符数组并排序
char[] chars = str.toCharArray();
Arrays.sort(chars);
// 将排序后的字符数组转回字符串,作为key
String key = new String(chars); // 如果key不存在,创建新的列表
if (!map.containsKey(key)) {
map.put(key, new ArrayList<>());
}
// 将原字符串添加到对应的组
map.get(key).add(str);
} // 返回所有分组的列表
return new ArrayList<>(map.values());
}

优化解法:计数法

还有一种更巧妙的解法。如果我们仔细观察,会发现:字母异位词的特点是每个字母出现的次数相同。

就像数纸牌时,我们不需要真的把牌排序,只需要数一下每种花色各有多少张。

计数法的原理

  1. 对于每个单词,统计其中每个字母出现的次数
  2. 将这个统计结果作为key(需要特殊的格式化方式)
  3. 相同统计结果的单词就是字母异位词

算法步骤(伪代码)

  1. 创建哈希表map存储分组结果
  2. 对于每个单词:
    • 创建一个大小为26的计数数组
    • 统计每个字母的出现次数
    • 将计数数组转换为特殊格式的字符串作为key
    • 将单词加入对应的分组
  3. 返回所有分组

示例运行

让我们模拟处理输入:[“eat”, “tea”, “tan”]

处理"eat":
- 计数:{a:1, e:1, t:1, 其他:0}
- key = "1#1#0#0#1#0#..."(代表a1b0c0d0e1...)
- 创建新组:{"1#1#0#0#1#0...": ["eat"]} 处理"tea":
- 计数相同:{a:1, e:1, t:1, 其他:0}
- 找到相同的key,加入该组
- {"1#1#0#0#1#0...": ["eat","tea"]} 处理"tan":
- 计数:{a:1, n:1, t:1, 其他:0}
- 新的key,创建新组
- map添加新组

Java代码实现

public List<List<String>> groupAnagrams(String[] strs) {
// 创建哈希表存储分组
Map<String, List<String>> map = new HashMap<>(); for (String str : strs) {
// 创建字符计数数组
int[] count = new int[26];
// 统计每个字符出现的次数
for (char c : str.toCharArray()) {
count[c - 'a']++;
} // 构建key
StringBuilder key = new StringBuilder();
for (int i = 0; i < 26; i++) {
key.append(count[i]).append('#');
}
String keyStr = key.toString(); // 添加到对应的分组
if (!map.containsKey(keyStr)) {
map.put(keyStr, new ArrayList<>());
}
map.get(keyStr).add(str);
} return new ArrayList<>(map.values());
}

排序法vs计数法

让我们比较这两种解法:

排序法的时间复杂度是O(nklogk),其中n是单词数量,k是最长单词的长度。主要时间花在对每个单词进行排序上。它的优点是直观易懂,而且如果单词长度不大,性能也不错。

计数法的时间复杂度是O(nk),因为我们只需要遍历每个单词一次,统计字母出现次数。虽然实现稍微复杂一些,但在处理长单词时更有优势。

题目模式总结

这道题体现了一个重要的算法模式:通过某种标准形式来对对象分组

这种模式在实际编程中经常出现,比如:

  • 按文件大小分类存储文件
  • 对用户行为数据进行分组分析
  • 整理图书馆的图书分类

解决这类问题的通用思路是:

  1. 设计一个合适的标准形式(可以是排序后的结果,也可以是某种统计特征)
  2. 用哈希表存储分组结果
  3. 选择合适的数据结构来表示标准形式(字符串、数组等)

小结

通过这道题,我们不仅学会了如何解决字母异位词分组问题,更重要的是理解了"标准形式分组"这一重要的算法思想。这种思想在很多实际问题中都能派上用场。

记住,写代码时多思考如何把问题抽象成更一般的模式,这样才能举一反三,提高解决问题的能力!


作者:忍者算法
公众号:忍者算法

从整理扑克牌到字母异位词分组:一道巧妙的排序应用题 |LeetCode 49 字母异位词分组的更多相关文章

  1. LeetCode 49: 字母异位词分组 Group Anagrams

    LeetCode 49: 字母异位词分组 Group Anagrams 题目: 给定一个字符串数组,将字母异位词组合在一起.字母异位词指字母相同,但排列不同的字符串. Given an array o ...

  2. Java实现 LeetCode 49 字母异位词分组

    49. 字母异位词分组 给定一个字符串数组,将字母异位词组合在一起.字母异位词指字母相同,但排列不同的字符串. 示例: 输入: ["eat", "tea", & ...

  3. Leetcode 49.字母异位词分组

    字母异位词分组 给定一个字符串数组,将字母异位词组合在一起.字母异位词指字母相同,但排列不同的字符串. 示例: 输入: ["eat", "tea", " ...

  4. LeetCode 49. 字母异位词分组(Group Anagrams)

    题目描述 给定一个字符串数组,将字母异位词组合在一起.字母异位词指字母相同,但排列不同的字符串. 示例: 输入: ["eat", "tea", "ta ...

  5. LeetCode:字母异位词分组【16】

    LeetCode:字母异位词分组[16] 题目描述 给定一个字符串数组,将字母异位词组合在一起.字母异位词指字母相同,但排列不同的字符串. 示例: 输入: ["eat", &quo ...

  6. 【leetcode】字母异位词分组

    给定一个字符串数组,将字母异位词组合在一起.字母异位词指字母相同,但排列不同的字符串. 示例: 输入: ["eat", "tea", "tan&quo ...

  7. 【LeetCode】49. 字母异位词分组

    49. 字母异位词分组 知识点:字符串:哈希表 题目描述 给你一个字符串数组,请你将 字母异位词 组合在一起.可以按任意顺序返回结果列表. 字母异位词 是由重新排列源单词的字母得到的一个新单词,所有源 ...

  8. [LeetCode] 49. Group Anagrams 分组变位词

    Given an array of strings, group anagrams together. For example, given: ["eat", "tea& ...

  9. Java实现 LeetCode 748 最短完整词(字母拆分+暴力)

    748. 最短完整词 如果单词列表(words)中的一个单词包含牌照(licensePlate)中所有的字母,那么我们称之为完整词.在所有完整词中,最短的单词我们称之为最短完整词. 单词在匹配牌照中的 ...

  10. [LeetCode] 249. Group Shifted Strings 分组偏移字符串

    Given a string, we can "shift" each of its letter to its successive letter, for example: & ...

随机推荐

  1. HAR文件

    简介 HAR(HTTP Archive format),是一种或 JSON 格式的存档格式文件,通用扩展名为 .har.Web 浏览器可以使用该格式导出有关其加载的网页的详细性能数据. 使用场景 在开 ...

  2. golang之json.RawMessage

    RawMessage 具体来讲是 json 库中定义的一个类型.它实现了 Marshaler 接口以及 Unmarshaler 接口,以此来支持序列化的能力.注意上面我们引用 官方 doc 的说明. ...

  3. 关于elementUI的table报错RangeErr Maximum call stack size exceeded

    项目中需要做一个功能,在表格中如果存在二级列表,点击箭头之后请求后台接口,展开显示二级列表的内容.点击箭头拿到了数据,但是后台会报错如下图,且数据展示不出来 上网查了下,意思是堆栈溢出,这个提示让我十 ...

  4. mongo迁移工具之mongo-shake

    最近需要进行MongoDB中数据迁移,之前使用过阿里系的redisShake感觉不错, 这次打算使用mongoShake来进行同步 github: https://github.com/alibaba ...

  5. Htq-基于Node.js的异步队列

    github: https://github.com/star7th/htq 部分介绍: 先介绍下基本概念. 我们在编写程序时,偶尔会遇到需要用到异步队列的情况.比如说,我发送一万封邮件,如果单纯使用 ...

  6. linux模拟HID USB设备及wireshark USB抓包配置

    目录 1. 内核配置 2. 设备配置 附 wireshark USB抓包配置 笔者开发USB设备时的一些记录 1. 内核配置 内核启用USB Gadget,使用fs配置usb device信息. De ...

  7. 这些 JavaScript 编码习惯,让你最大程度提高你的项目可维护性!

    前言: 因为 JavaScript 语言是一门极其松散.极其自由的语言,这意味着我们可以随心所欲的操作它,这是他的优点,但同时也是它的缺点.在编码过程中,我们需要一种良好的规范或者习惯来保持应用程序的 ...

  8. 【C++】static 知识整理 【静态与局部静态】

    目录 类外 类内 局部静态 local static 类外 类内 类外 C++的静态可以分为两种情况来讨论:在类外和在类内. 对于静态变量/函数,链接将只在内部 (如果不用static,那么在不同文件 ...

  9. uni-app小程序项目使用iconfont字体图标

    前情 uni-app是我比较喜欢的跨平台框架,它能开发小程序/H5/APP(安卓/iOS),重要的是对前端开发友好,自带的IDE让开发体验非常棒,公司项目就是主推uni-app. 为什么要这么做? 借 ...

  10. 配置 Forwarded Headers Middleware

    来自微软的说明:Configure ASP.NET Core to work with proxy servers and load balancers | Microsoft Learn. 通过该中 ...