
本文详细阐述了一种在java中高效分组同字母异位词的方法,该方法通过利用字符频率计数数组作为哈希映射的键,避免了对字符串进行排序,从而优化了性能。文章深入探讨了字符频率数组如何作为唯一标识符,并提供了具体的代码实现、详细的时间复杂度分析以及相关注意事项,旨在为开发者提供一个清晰且专业的教程。
在编程挑战中,分组同字母异位词(Group Anagrams)是一个常见问题。同字母异位词是指由相同字母但顺序不同的字符串组成,例如 "eat"、"tea" 和 "ate" 都是同字母异位词。解决这类问题的一个直观方法是对每个字符串进行排序,然后将排序后的字符串作为哈希表的键。然而,这种方法的时间复杂度通常较高,因为它涉及到对每个字符串进行排序操作。本文将介绍一种更高效的无排序方法,通过字符频率计数来为同字母异位词生成唯一的哈希键。
同字母异位词的本质在于它们拥有相同的字符集合和每个字符的相同出现次数。例如,"listen" 和 "silent" 都包含一个 'l'、一个 'i'、一个 's'、一个 't'、一个 'e' 和一个 'n'。基于这一特性,我们可以创建一个“签名”来唯一标识一组同字母异位词,而无需对字符串本身进行排序。
这个“签名”就是字符频率计数数组。对于只包含小写英文字母的字符串,我们可以使用一个长度为 26 的整型数组来记录每个字母的出现次数。数组的每个索引对应一个字母(例如,索引 0 对应 'a',索引 1 对应 'b',以此类推),存储该字母在字符串中出现的次数。
例如,对于字符串 "eat":
立即学习“Java免费学习笔记(深入)”;
对应的频率数组将是 [1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0](其中第0位是'a',第4位是'e',第19位是't')。所有与 "eat" 是同字母异位词的字符串,如 "tea" 和 "ate",都会生成完全相同的频率数组。
为了将这个频率数组用作 HashMap 的键,我们需要将其转换为一个不可变的、可哈希的对象,最常见且方便的方式是将其转换为 String 类型。Java 的 Arrays.toString(int[] a) 方法可以将一个整型数组转换为其字符串表示形式,例如 [1, 0, 0, ..., 1]。这个字符串表示形式将作为 HashMap 的键,而其值则是一个 List<String>,用于存储所有具有相同频率数组(即同字母异位词)的原始字符串。
以下是基于上述思想的 Java 实现:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class AnagramGrouper {
/**
* 将一组字符串中的同字母异位词进行分组。
*
* @param strs 待分组的字符串数组。
* @return 包含同字母异位词分组的列表。
*/
public List<List<String>> groupAnagrams(String[] strs) {
// 最终结果列表
List<List<String>> output = new ArrayList<>();
// 处理空输入情况
if (strs == null || strs.length == 0) {
return output;
}
// 使用 HashMap 存储分组结果
// 键:字符频率数组的字符串表示 (e.g., "[1, 0, ..., 1]")
// 值:包含所有对应同字母异位词的列表
Map<String, List<String>> outputMap = new HashMap<>();
// 遍历输入字符串数组
for (String str : strs) {
// 创建一个长度为 26 的数组,用于记录每个小写字母的出现次数
// 索引 0 -> 'a', 索引 1 -> 'b', ..., 索引 25 -> 'z'
int[] charCounts = new int[26];
// 遍历当前字符串的每个字符,更新 charCounts 数组
for (int i = 0; i < str.length(); i++) {
charCounts[str.charAt(i) - 'a']++;
}
// 将字符频率数组转换为字符串,作为 HashMap 的键
// 例如,对于 "eat",charCounts 可能是 [1,0,0,0,1,...,1],
// Arrays.toString() 会将其转换为 "[1, 0, 0, 0, 1, 0, ..., 1, 0, 0, 0, 0, 0, 0]"
String key = Arrays.toString(charCounts);
// 检查 HashMap 中是否已存在该键
if (outputMap.containsKey(key)) {
// 如果存在,说明找到了一个同字母异位词,将其添加到对应的列表中
outputMap.get(key).add(str);
} else {
// 如果不存在,创建一个新的列表,将当前字符串添加进去,并将其作为新的键值对存入 HashMap
List<String> newList = new ArrayList<>();
newList.add(str);
outputMap.put(key, newList);
}
}
// 将 HashMap 中所有的值(即所有的同字母异位词列表)收集到最终结果列表中
output.addAll(outputMap.values());
return output;
}
public static void main(String[] args) {
AnagramGrouper grouper = new AnagramGrouper();
String[] strs1 = {"eat", "tea", "tan", "ate", "nat", "bat"};
List<List<String>> result1 = grouper.groupAnagrams(strs1);
System.out.println("示例 1 分组结果: " + result1);
// 预期输出可能为: [[bat], [nat, tan], [ate, eat, tea]] 或类似顺序
String[] strs2 = {""};
List<List<String>> result2 = grouper.groupAnagrams(strs2);
System.out.println("示例 2 分组结果: " + result2); // 预期输出: [[]]
String[] strs3 = {"a"};
List<List<String>> result3 = grouper.groupAnagrams(strs3);
System.out.println("示例 3 分组结果: " + result3); // 预期输出: [[a]]
}
}该算法的时间复杂度主要由两个嵌套循环决定:
因此,总的时间复杂度为 m * (n + 1 + 1 + 1),简化后为 m * (n + 3)。去除常数项后,最终的时间复杂度为 *O(m n)**。
与传统的排序方法(通常为 O(m * n log n))相比,这种基于字符频率计数的方法在字符串长度较大时具有显著的性能优势。
综合来看,空间复杂度为 *O(m n)**。
通过利用字符频率计数数组作为哈希映射的键,我们能够有效地分组同字母异位词,避免了耗时的字符串排序操作。这种方法不仅提供了清晰的逻辑,而且在时间复杂度上达到了 O(m * n),使其成为解决此类问题的推荐方案。理解其核心原理和实现细节,对于开发者在处理字符串和哈希表相关问题时具有重要的指导意义。
以上就是Java实现无排序分组同字母异位词:哈希映射与字符计数详解的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号