问题描述
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的所有字母得到的一个新单词。
示例 1:
输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"] 输出: [["bat"],["nat","tan"],["ate","eat","tea"]]
示例 2:
输入: strs = [""] 输出: [[""]]
示例 3:
输入: strs = ["a"] 输出: [["a"]]
提示:
1 <= strs.length <= 104
0 <= strs[i].length <= 100
strs[i]
仅包含小写字母
方法一:HashMap + 字符串排序
遍历字符串数组,将每一个字符串排序后,作为键值存入map,最后再转换返回。
外层循环遍历为O(n),内层排序Arrays.sort使用的是TimSort 算法,时间复杂度为O(k log k),k为单个字符串最大长度,因此总时间复杂度为O(nk log k)。
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
Map<String, List<String>> map = new HashMap<>();
for (String str : strs) {
// 字符串排序后,再转回字符串
char[] arrays = str.toCharArray();
Arrays.sort(arrays);
String key = new String(arrays);
// 如果map中不存在key,则创建一个空的list,并将str添加到list中,并将list添加到map中
List<String> list = map.getOrDefault(key, new ArrayList<>());
list.add(str);
map.put(key, list);
}
// 将map转换为 List<List<String>> 类型返回
return new ArrayList<>(map.values());
}
}
方法二:HashMap + 计数
根据题意仅包含小写字母,那么其实可以使用一个字母表,优化掉内层的Arrays.sort,即用一个长度为26的数组记录,每个字符串中字符次数,最后再将该数组转为字符串作为map的键(ps.忽略掉数组中次数为0的字符还可以减少一点空间复杂度)。
优化后内层的时间复杂度为O(k+26),总时间复杂度为O(n(k+26))。
当log k > 26 时,方法二更佳。
class Solution {
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']++;
}
// 将count数组转换为字符串,作为key,忽略掉次数为0的可以减少一点空间复杂度
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 26; i++) {
if (count[i] != 0) {
sb.append((char) ('a' + i));
sb.append(count[i]);
}
}
String key = sb.toString();
// 如果map中不存在key,则创建一个空的list,并将str添加到list中,并将list添加到map中
List<String> list = map.getOrDefault(key, new ArrayList<>());
list.add(str);
map.put(key, list);
}
// 将map转换为 List<List<String>> 类型返回
return new ArrayList<>(map.values());
}
}
性能优化:为什么选择StringBuider而不是StringBuffer和String?
首先说String,如果用String变量去+的话,每次都会创建一个新的String变量赋值。
而StringBuider和StringBuffer的话内部维护了一个byte数组,最后调用toString方法才会新建String变量赋值。
之所以选择StringBuider而不是StringBuffer是因为,二者的区别仅在于StringBuffer给每个方法都添加了sychronized关键字以确保多线程安全,但是会影响性能。
单线程时不需要考虑线程安全问题,因此StringBuider更好。
(by 归忆)