从整理扑克牌到字母异位词分组:一道巧妙的排序应用题 |LeetCode 49 字母异位词分组
LeetCode 49 字母异位词分组
点此看全部题解 LeetCode必刷100题:一份来自面试官的算法地图(题解持续更新中)
生活中的算法
你有没有玩过扑克牌?打完一局之后,我们通常会把散落的牌收集起来,按照花色分组整理。虽然每张牌的花色图案位置可能不同,但只要是同一个花色,我们就把它们放在一起。
这和我们今天要讲的"字母异位词分组"问题很像。字母异位词就像是同一花色的扑克牌,它们由相同的字母组成,只是字母的排列顺序不同。
问题描述
LeetCode第49题"字母异位词分组"是这样描述的:给你一个字符串数组,请你将所有的字母异位词放在同一个组里。字母异位词是由相同的字母重新排列组成的单词。
例如,“eat”、"ate"和"tea"就是字母异位词,因为它们都由字母’a’、‘e’、't’组成,只是排列顺序不同。
最直观的解法:排序比较法
如何判断两个单词是否是字母异位词?最直观的方法就是:把两个单词的字母都排序后比较,如果排序后相同,那就是字母异位词。
就像整理扑克牌时,我们会把每张牌的花色符号位置摆正,这样就容易看出它们是不是同一花色。
具体步骤是这样的:
- 对于每个单词,将其字符排序得到一个标准形式
- 使用这个标准形式作为key,原单词作为value,存入哈希表
- 具有相同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());
}
优化解法:计数法
还有一种更巧妙的解法。如果我们仔细观察,会发现:字母异位词的特点是每个字母出现的次数相同。
就像数纸牌时,我们不需要真的把牌排序,只需要数一下每种花色各有多少张。
计数法的原理
- 对于每个单词,统计其中每个字母出现的次数
- 将这个统计结果作为key(需要特殊的格式化方式)
- 相同统计结果的单词就是字母异位词
算法步骤(伪代码)
- 创建哈希表map存储分组结果
- 对于每个单词:
- 创建一个大小为26的计数数组
- 统计每个字母的出现次数
- 将计数数组转换为特殊格式的字符串作为key
- 将单词加入对应的分组
- 返回所有分组
示例运行
让我们模拟处理输入:[“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),因为我们只需要遍历每个单词一次,统计字母出现次数。虽然实现稍微复杂一些,但在处理长单词时更有优势。
题目模式总结
这道题体现了一个重要的算法模式:通过某种标准形式来对对象分组。
这种模式在实际编程中经常出现,比如:
- 按文件大小分类存储文件
- 对用户行为数据进行分组分析
- 整理图书馆的图书分类
解决这类问题的通用思路是:
- 设计一个合适的标准形式(可以是排序后的结果,也可以是某种统计特征)
- 用哈希表存储分组结果
- 选择合适的数据结构来表示标准形式(字符串、数组等)
小结
通过这道题,我们不仅学会了如何解决字母异位词分组问题,更重要的是理解了"标准形式分组"这一重要的算法思想。这种思想在很多实际问题中都能派上用场。
记住,写代码时多思考如何把问题抽象成更一般的模式,这样才能举一反三,提高解决问题的能力!
作者:忍者算法
公众号:忍者算法
从整理扑克牌到字母异位词分组:一道巧妙的排序应用题 |LeetCode 49 字母异位词分组的更多相关文章
- LeetCode 49: 字母异位词分组 Group Anagrams
LeetCode 49: 字母异位词分组 Group Anagrams 题目: 给定一个字符串数组,将字母异位词组合在一起.字母异位词指字母相同,但排列不同的字符串. Given an array o ...
- Java实现 LeetCode 49 字母异位词分组
49. 字母异位词分组 给定一个字符串数组,将字母异位词组合在一起.字母异位词指字母相同,但排列不同的字符串. 示例: 输入: ["eat", "tea", & ...
- Leetcode 49.字母异位词分组
字母异位词分组 给定一个字符串数组,将字母异位词组合在一起.字母异位词指字母相同,但排列不同的字符串. 示例: 输入: ["eat", "tea", " ...
- LeetCode 49. 字母异位词分组(Group Anagrams)
题目描述 给定一个字符串数组,将字母异位词组合在一起.字母异位词指字母相同,但排列不同的字符串. 示例: 输入: ["eat", "tea", "ta ...
- LeetCode:字母异位词分组【16】
LeetCode:字母异位词分组[16] 题目描述 给定一个字符串数组,将字母异位词组合在一起.字母异位词指字母相同,但排列不同的字符串. 示例: 输入: ["eat", &quo ...
- 【leetcode】字母异位词分组
给定一个字符串数组,将字母异位词组合在一起.字母异位词指字母相同,但排列不同的字符串. 示例: 输入: ["eat", "tea", "tan&quo ...
- 【LeetCode】49. 字母异位词分组
49. 字母异位词分组 知识点:字符串:哈希表 题目描述 给你一个字符串数组,请你将 字母异位词 组合在一起.可以按任意顺序返回结果列表. 字母异位词 是由重新排列源单词的字母得到的一个新单词,所有源 ...
- [LeetCode] 49. Group Anagrams 分组变位词
Given an array of strings, group anagrams together. For example, given: ["eat", "tea& ...
- Java实现 LeetCode 748 最短完整词(字母拆分+暴力)
748. 最短完整词 如果单词列表(words)中的一个单词包含牌照(licensePlate)中所有的字母,那么我们称之为完整词.在所有完整词中,最短的单词我们称之为最短完整词. 单词在匹配牌照中的 ...
- [LeetCode] 249. Group Shifted Strings 分组偏移字符串
Given a string, we can "shift" each of its letter to its successive letter, for example: & ...
随机推荐
- CommonsCollections3(基于ysoserial)
环境准备 JDK1.7(7u80).commons-collections(3.x 4.x均可这里使用3.2版本).javassist(3.12.1.GA) JDK:https://repo.huaw ...
- nvidia公司的机器人仿真环境的历史发展介绍(Isaac-Gym、Isaac-Sim)
相关: NVIDIA机器人仿真项目 -- Isaac Gym - Preview Release 本文说下NVIDIA公司的机器人仿真项目的一些历史发展. NVIDIA公司的产品最初只有显卡,但是卖着 ...
- 用python 将数字每三组分割
def cut_num(n, sep=','): # 第一个参数是要传递的数字,第二个是分隔符 s = str(abs(n))[::-1] groups = [] i = 0 while i < ...
- 从架构到成本,SQL Server 和 PostgreSQL 四大区别全方位解析!
从架构到成本,SQL Server 和 PostgreSQL 四大区别全方位解析! 今天我想分享 SQL Server 和 PostgreSQL 之间的四大关键区别. 在比较 SQL Server 和 ...
- 我对Java内存模型的理解
所有的编程语言中都有内存模型这个概念,区别于微架构的内存模型,高级语言的内存模型包括了编译器和微架构两部分.我试图了解了Java.C#和Go语言的内存模型,发现内容基本大同小异,只是这些语言在具体实现 ...
- JavaCC 研究与应用( 8000字 心得 源程序)
一.JavaCC简介 JavaCC(Java Compiler Compiler)是一个用JAVA开发的最受欢迎的语法分析生成器.这个分析生成器工具可以读取上下文无关且有着特殊意义的语法并把它转换成可 ...
- Linux 文件删除空间没有释放问题
最近阿里云频频告警.磁盘使用率飙升90%以上.遂查看磁盘使用情况 df -h 发现使用率却是很高 之后,通过du -h --max-depth=1 / 查看哪个目录下占用的资源较多并进行删除 后来发现 ...
- sort函数详解
sort函数 简介 其实STL中的sort()并非只是普通的快速排序,除了对普通的快速排序进行优化,它还结合了插入排序和堆排序.根据不同的数量级别以及不同情况,能自动选用合适的排序方法.当数据量较大时 ...
- SpringBoot 整合Swagger2 踩坑记录
SpringBoot 整合Swagger2 踩坑记录 Failed to start bean 'documentationPluginsBootstrapper'; nested exception ...
- 通过本地私有的镜像仓库harbor解决网络原因导致的jdk无法加载而造成的docker打包错误
各种网络原因,或是docker.io无法访问,或是阿里的镜像源故障,导致java打包发布的时候报错,很影响代码发布的质量. 解决思路:墙出去把jdk下载下来,代码使用本地的harbor库进行引用,一 ...