组合在一起。可以按任意顺序返回结果列表。
示例 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 <= 1040 <= strs[i].length <= 100strs[i]仅包含小写字母
方法一:排序法(推荐)
- 创建一个哈希表(Map/Dictionary) 来存储分组结果
- key:字符串排序后的结果
- value:原始字符串的列表
- 遍历字符串数组,对每个字符串:
- 将字符串的字符进行排序(例如 "eat" → "aet")
- 排序后的字符串作为 key
- 将原始字符串添加到对应 key 的列表中
- 为什么这样可行?
- "eat"、"tea"、"ate" 排序后都是 "aet"
- 它们会被映射到同一个 key 下
- 而 "bat" 排序后是 "abt",会有不同的 key
- 最后返回哈希表中所有的 value 列表
时间复杂度:O(n × k log k),其中 n 是字符串数量,k 是字符串最大长度(排序需要 k log k)
方法二:字符计数法
- 同样用哈希表存储
- 对每个字符串统计字符出现次数
- 例如 "eat" → a:1, e:1, t:1
- 将这个计数结果转换成一个唯一的标识(比如 "a1e1t1" 或者用数组 [1,0,0,0,1,0,...,1])
- 用这个标识作为 key 进行分组
时间复杂度:O(n × k),更快,但实现稍微复杂一点
示例推演
对于 ["eat", "tea", "tan", "ate", "nat", "bat"]:使用排序法:
- "eat" → 排序 → "aet" → 放入 map["aet"]
- "tea" → 排序 → "aet" → 放入 map["aet"]
- "tan" → 排序 → "ant" → 放入 map["ant"]
- "ate" → 排序 → "aet" → 放入 map["aet"]
- "nat" → 排序 → "ant" → 放入 map["ant"]
- "bat" → 排序 → "abt" → 放入 map["abt"]
最后得到:
- map["aet"] = ["eat", "tea", "ate"]
- map["ant"] = ["tan", "nat"]
- map["abt"] = ["bat"]
返回所有的 value 即可。
这边选择方法一
public List<List<String>> groupAnagrams(String[] strs) {
// 1. 创建哈希表用于分组
// key: 排序后的字符串(作为"身份证")
// value: 具有相同"身份证"的原始字符串列表
Map<String, List<String>> map = new HashMap<>();
// 2. 遍历输入数组中的每个字符串
for (String str : strs) {
// 2.1 将字符串转换为字符数组(为了排序)
char[] charArray = str.toCharArray();
// 2.2 对字符数组进行排序
// 例如: "eat" -> ['e','a','t'] -> ['a','e','t']
Arrays.sort(charArray);
// 2.3 将排序后的字符数组转回字符串,作为key
// 例如: ['a','e','t'] -> "aet"
String sortedStr = new String(charArray);
// 2.4 在哈希表中查找这个key
// 如果不存在,就创建一个新的ArrayList
// 如果存在,就获取已有的列表
if (!map.containsKey(sortedStr)) {
map.put(sortedStr, new ArrayList<>());
}
// 2.5 将原始字符串添加到对应的列表中
// 例如: "eat" 添加到 map.get("aet") 的列表中
map.get(sortedStr).add(str);
}
// 3. 将哈希表中所有的value(列表)提取出来,组成最终结果
// new ArrayList<>(map.values()) 会创建一个新的列表,包含所有的分组
return new ArrayList<>(map.values());
}
今天学到两个知识
1.Arrays.sort() 的工作原理
Arrays.sort(charArray) 会对整个字符数组进行完整的排序,不是部分排序。会保证整个数组从小到大完全有序。
2.Map 的结构
Map 的每个 value 本身就是一个 List(列表)
因为是Map<String,List<String>> map =new HashMap<>() 所以能输出一组一组的
// 初始化空 map
Map<String, List<String>> map = new HashMap<>();
// ====== 处理 "eat" ======
String str = "eat";
String sortedStr = "aet"; // 排序后
// 此时 map 中没有 "aet" 这个 key
if (!map.containsKey("aet")) {
// 创建一个新的 ArrayList,并放入 map
map.put("aet", new ArrayList<>());
}
// 现在 map = {"aet": []} ← 空列表
// 把 "eat" 添加到这个列表中
map.get("aet").add("eat");
// 现在 map = {"aet": ["eat"]}
// ====== 处理 "tea" ======
String str = "tea";
String sortedStr = "aet"; // 排序后还是 "aet"
// map 中已经有 "aet" 了,跳过 if
// 直接获取 "aet" 对应的列表,然后添加 "tea"
map.get("aet").add("tea");
// 现在 map = {"aet": ["eat", "tea"]}
// ↑ 同一个列表对象
// ====== 处理 "ate" ======
String str = "ate";
String sortedStr = "aet"; // 还是 "aet"
map.get("aet").add("ate");
// 现在 map = {"aet": ["eat", "tea", "ate"]}
// ↑ 还是同一个列表对象
// ====== 处理 "bat" ======
String str = "bat";
String sortedStr = "abt"; // 新的 key
// map 中没有 "abt"
map.put("abt", new ArrayList<>());
map.get("abt").add("bat");
// 现在 map = {
// "aet": ["eat", "tea", "ate"],
// "abt": ["bat"]
// }
// ====== 处理 "tan" ======
String str = "tan";
String sortedStr = "ant"; // 又一个新 key
map.put("ant", new ArrayList<>());
map.get("ant").add("tan");
// 现在 map = {
// "aet": ["eat", "tea", "ate"],
// "abt": ["bat"],
// "ant": ["tan"]
// }
// ====== 处理 "nat" ======
String str = "nat";
String sortedStr = "ant"; // 已存在的 key
map.get("ant").add("nat");
// 最终 map = {
// "aet": ["eat", "tea", "ate"],
// "abt": ["bat"],
// "ant": ["tan", "nat"]
// }
Map 内部:
┌─────────────────────────────────────────┐
│ Key Value │
│ ──── ───── │
│ "aet" → ["eat", "tea", "ate"] │
│ "abt" → ["bat"] │
│ "ant" → ["tan", "nat"] │
└─────────────────────────────────────────┘
map.values() 提取所有 value:
┌─────────────────────────┐
│ ["eat", "tea", "ate"] │
│ ["bat"] │
│ ["tan", "nat"] │
└─────────────────────────┘
new ArrayList<>(map.values()) 包装成大列表:
┌────────────────────────────────────┐
│ List<List<String>> │
│ ────────────────────── │
│ [0] → ["eat", "tea", "ate"] │
│ [1] → ["bat"] │
│ [2] → ["tan", "nat"] │
└────────────────────────────────────┘
浙公网安备 33010602011771号