目录
List list = map.getOrDefault(key, new ArrayList());
一 题目描述
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的所有字母得到的一个新单词。
示例 1:
输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"] 输出: [["bat"],["nat","tan"],["ate","eat","tea"]]
示例 2:
输入: strs = [""] 输出: [[""]]
示例 3:
输入: strs = ["a"] 输出: [["a"]]
二 方法代码
说在前面的两个知识点
-
List<String> list = map.getOrDefault(key, new ArrayList<String>());
首先执行map.get(key),从 map
中获取与 key
关联的值。如果 key
存在于 map
中,它将返回一个 List<String>
。如果 key
不存在于 map
中,它将返回 null,然后创建一个空列表
。
如果我们不使用 getOrDefault
,我们需要手动检查 map.get(key)
是否为 null
,如果是,则需要初始化一个新的 ArrayList
。getOrDefault
方法自动处理了这个问题,简化了逻辑。
-
new ArrayList<List<String>>(map.values());
map.values()
方法返回的是 HashMap
中所有值的集合(Collection
)。这个集合包含了 HashMap
中所有与键关联的值。
这段代码的作用是:
- 从
HashMap
中获取所有的值(即List<String>
)。 - 创建一个新的
ArrayList<List<String>>
,并将HashMap
的值集合Collection<List<String>>
复制到这个新的ArrayList
中。 - 最终得到了一个包含
HashMap
中所有值的ArrayList
。
-
方法一(我自己的方法,效率并不好,可以看第二个官方方法)
我的思路是这样的,我一开始看到了标签时哈希表,我就在想这个怎么能联系到哈希表,我首先想的是str作为key,那选择什么作为value。要找出包含字符相同的字符串,我很快就想到了ASCII,我一开始想的是表示ASCII的和作为value,但是一想a、d的ASCII和与b、c的ADSCII和一样,所以苦于怎么进一步改善
后面看到哈希表的value可以定义为列表,我就想到了用从小到大的ASCII列表表示字符顺序不同但是一致字符的字符串了,就有了以下方法,但是效率不高(原因见第三点)
这个排序的ASCII列表表示key,这样每遇到一个相同的key,就刷新value的list,把这个字符串加入到这个list中
public List<List<String>> groupAnagrams(String[] strs) {
// 注意map的key时列表,表示字符串的字母ASCII值的大小顺序
Map<List<Integer>, List<String>> map = new HashMap<>();
for (String str : strs) {
// 用来按大小顺序存储每一个字符的ASCII值
List<Integer> asciiOrder = new ArrayList<Integer>();
for (char c : str.toCharArray()) {
int num = 0;
// num表示指针,从列表中第一个数开始,找到合适位置插入
while (num < asciiOrder.size()) {
// (int)c表示拿去c的ASCII值
if ((int) c <= asciiOrder.get(num)) {
asciiOrder.add(num, (int) c);
break;
}
num++;
}
// 如果num等于长度,说明所有的数都小于c的ASCII值,直接插入到最后
if (num == asciiOrder.size()) {
asciiOrder.add((int) c);
}
// getOrDefault会判断map中有没有asciiOrder这个key
// 如果有,就把asciiOrder对应的列表赋值给list
// 如果没有,就默认创建一个空列表list
// 如果已经有这个asciiOrder的key时,就拿出其对应的列表,然后把str加入到后面
}
List<String> list = map.getOrDefault(asciiOrder, new ArrayList<>());
list.add(str);
map.put(asciiOrder, list);
}
return new ArrayList<List<String>>(map.values());
}
-
方法二(排序,力扣官方解法)
思路大致就是,把每一个字符串按照字符的ASCII值排序重新组为一个字符串,这个排序的字符串表示key,这样每遇到一个相同的key,就刷新value的list,把这个字符串加入到这个list中
public List<List<String>> groupAnagrams1(String[] strs) {
Map<String, List<String>> map = new HashMap<>();
for(String str : strs){
char[] chars = str.toCharArray();
Arrays.sort(chars);
String sorted = new String(chars);
// getOrDefault会判断map中有没有sorted这个key
// 如果有,就把sorted对应的列表赋值给list
// 如果没有,就默认创建一个空列表list
// 这样如果已经有这个sorted的key时,就拿出其对应的列表,然后把str加入到后面
List<String> list = map.getOrDefault(sorted,new ArrayList<>());
list.add(str);
//HashMap会覆盖key对应的关系,也就是更新value即加入了当前str的list
map.put(sorted,list);
}
return new ArrayList<List<String>>(map.values());
}
三 运行结果
原因分析:
1.键的复杂性和比较成本
我自己的方法用一个列表做key,List是一个复杂对象,因此每次比较key时需要遍历列表,时间复杂度很高
排序方法用String做key,相对简单的对象,查找和比较效率很高
2.排序成本
我自己的方法通过手动插入排序的方式将字符的 ASCII 值按顺序插入到 asciiOrder
列表中。这种手动排序的方式效率较低,尤其是当字符串长度较大时,插入排序的时间复杂度接近 O(n²)。
排序方法调用 Arrays.sort(chars)
对字符数组进行排序。Arrays.sort
使用的是一种优化过的双轴快速排序(Dual-Pivot Quicksort)算法,时间复杂度为 O(n log n)。
3.内存
我的方法每次处理字符串时,会创建一个新的 List<Integer>
对象,并且需要动态调整和插入元素。这会增加内存使用和对象分配的开销。
排序方法只是创建了一个字符数组 char[]
,并对其进行排序。字符数组是一个基本数据类型的数组,因此内存使用更为高效,且不涉及对象包装的开销。
4. HashMap
操作的效率
我的方法,由于键是 List<Integer>
,HashMap
需要通过调用 List
的 hashCode()
方法生成哈希值,并通过 equals()
方法进行键比较。这些操作在处理复杂对象(如 List
)时效率较低。
排序方法,键是 String
,String
类的 hashCode()
和 equals()
方法经过高度优化,能够快速计算哈希值和进行比较。因此,HashMap
的操作在这种情况下会更加高效。