那当然了,毕竟都用的是hashmap解决的()。
来看看这道题吧:
1.分析题干:什么是字母异位词?
首先分析一下题干:字母异位词 是由重新排列源单词的所有字母得到的一个新单词。听着人都晕了,别看他说的好像很高大尚,其实内核很简单:举个例子,abc,acb,bac这三个词就是字母异位词,因为他们字母种类和个数都相同。也就是说,你可以这么理解:字母异位词就是 字符集相同的词。
这里我自己瞎定义了一个概念,叫字符集。其实也好理解,就是单词里所有字母的集合嘛。但为了表示几个单词的字符集相同,它就应该是有序的。回到例子上就是,abc,acb,bac三个词的字符集都是abc。
把这个概念绕清楚,题目瞬间就简化了:就是让你把字符集相同的词分在一组,作为一个List.最后返回多个list组成的集合。
2.思路:为什么要使用hashmap,怎样合理使用hashmap?
如果这个热题没有打上hash的标签,我们怎么去想到使用hashmap?其实也很简单,思考一下这个题目,第一直觉应该是要这么做:遍历原字符串数组中的每一个字符串,如果之前已经遍历过同样的字符集,就应该找到那个字符集对应的list,并加入到这个list中。如果之前没有遍历过这样的字符集,就新建一个list,再加入到这个list中。
我们说的很简单,但怎样去获取一个字符串的字符集?(比如怎样把acb转化成字符集abc?)得到了这个字符集,我又怎么获取这个字符集对应的list?
1)怎样获取字符集
其实不难,使用排序就可以了嘛。自己手搓一个冒泡排序,或者使用Arrays工具类提供的sort方法,都能实现对字符数组的排序,也就转成了字符集。
比如以下就是一个冒泡排序获取字符串的字符集的实现:(冒泡排序就不做注释了)
/**
* 定义一个方法,这个方法用于获取字符串的升序字符集
* @param str 接收一个字符串
* @return 返回对应的字符集
*/
public String getCharSetOfStr(String str){
if(str.length() == 0){
return "";
}
char[] chars = str.toCharArray();
char temp;
for (int i = 0; i < chars.length - 1; i++) {
for (int j = 0; j < chars.length - 1 - i; j++) {
if (chars[j] > chars[j+1]){
temp = chars[j];
chars[j] = chars[j+1];
chars[j+1] = temp;
}
}
}
return new String(chars);
}
然后官方的题解中就是直接采用了Arrays.sort,两种方法都可以,大家参考一下。
2)怎样获取字符集对应的List?
这里就引出了我们为什么要使用hashmap:因为是典型的key-value结构。以字符集作为key,对应list在结果集中的索引作为value,那么每当我们拿到一个字符串的字符集,是不是就直接可以通过hashmap.get(key)的方式拿到list在结果集中的索引,再通过索引拿到list,然后将当前字符串加入list?
那如果hashmap中没有这个字符集的key呢?那就说明当前字符串是这个字符集里第一个被遍历到的嘛。直接新建一个list,将字符串加入list,然后把这个字符集和list的索引加入到hashmap。
OK,你已经做出了这道题了!虽然只是version1版本。
代码如下:
/**
* 所谓将字母异位词组合在一起,其实就是字符集相同的字符串放到一起
* 先new 一个ArrayList<List<String>>存放结果集
* 然后遍历原字符串数组中的每一个字符串,查找hashmap中是否存在同样的字符集
* 如果存在,则将这个字符串加入到对应字符集所在的List<String>中
* 字符集和字符集所在list的索引分别由hashmap中entry的key和value维护
* 如果不存在,则结果集add一个新的list用于存储这个字符集对应的字母异位词,
//并将这个字符集和list索引加入hashmap中
* 感觉完美
*
* 怎么获取一个字符串的字符集,并保证字母异位词对应同样的字符集?
* 我的想法是,ade,aed,dea这些字符串的字符集都是ade(按照字母升序排列)
* 获取这个升序字符集的方法是,对原字符串转为charArray后进行冒泡排序
*/
public List<List<String>> groupAnagrams(String[] strs) {
//1.创建结果集和hashmap,hashmap的key存放字符集,value存放list索引
ArrayList<List<String>> resList = new ArrayList<>();
HashMap<String, Integer> hashMap = new HashMap<>();
//2.遍历每一个字符串
for (String str : strs) {
//获取升序字符集
String charSet = getCharSetOfStr(str);//这个方法在下面定义了
//如果字符集在hashmap中存在
if (hashMap.containsKey(charSet)){
//找到这个字符集的List索引,将字符串加入
Integer index = hashMap.get(charSet);
resList.get(index).add(str);
//然后直接遍历下一个字符串
continue;
}
//如果字符集在hashmap中不存在,说明这是一个新的字符集;
//创建一个新的list<String>用于存放这个字符集对应的字母异位词,
//并将这个字符集保存到hashmap
ArrayList<String> newList = new ArrayList<>();
newList.add(str);
resList.add(newList);//将新list加入结果集resList
//获取newList在resList中的索引位置并放入hashmap
hashMap.put(charSet,resList.size() - 1);
}
//3.返回resList
return resList;
}
/**
* 定义一个方法,这个方法用于获取字符串的升序字符集
* @param str 接收一个字符串
* @return 返回对应的字符集
*/
public String getCharSetOfStr(String str){
if(str.length() == 0){
return "";
}
char[] chars = str.toCharArray();
char temp;
for (int i = 0; i < chars.length - 1; i++) {
for (int j = 0; j < chars.length - 1 - i; j++) {
if (chars[j] > chars[j+1]){
temp = chars[j];
chars[j] = chars[j+1];
chars[j+1] = temp;
}
}
}
return new String(chars);
}
3.本题如何优化?
1)获取字符集时,排序优化
你可能已经发现了最大的优化点:冒泡排序。n2的时间复杂度实在太大。优化方法也很简单,像官方一样Arrays.sort就可以将n2时间复杂度降到nlogn
2)改变对“字符集”的定义
上面使用“字符集”这个说法是因为比较容易想到,也比较容易理解(我自己第一次也是这么做的),但反过来想想,这样做真的是最优吗?
首先排序几乎达到n2的时间复杂度,再次,本题虽然str[i].length最大只有100,但往大了走,如果字符串有500个字符,或者更大,那我们的字符集不是也要达到500个字符?
所以就出现了第二种方案:计数法。我们在开始时也提到了,abc,acb,bac这三个词是字母异位词,因为他们字母种类和个数都相同。那么我们是不是可以通过字母种类和数字来组成更简洁、一次遍历就能获得的“新字符集”?比如,abbc我们用a1b2c1表示,即字母+出现次数,这样也同样能达到字母异位词的字符集相同的效果。是不是有点豁然开朗的感觉。
代码如下:(截取自官方题解)
for (String str : strs) {
int[] counts = new int[26];
int length = str.length();
for (int i = 0; i < length; i++) {
//比如counts[0]就代表当前字符串中‘a’出现的次数
counts[str.charAt(i) - 'a']++;
}
// 将每个出现次数大于 0 的字母和出现次数按顺序拼接成字符串,作为哈希表的键
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 26; i++) {
if (counts[i] != 0) {
sb.append((char) ('a' + i));
sb.append(counts[i]);
}
}
String key = sb.toString();
这样获取字符集的时间复杂度就变为了n,存储空间也占用更小了。
剩下的思路和之前的一模一样,hashmap的使用都是类似的,只是获取字符集的方法和时间复杂度不同。顺带一提,官方题解中hashmap的value直接就是存放了list本身,而我在做的时候是存放了list对应的索引,大家可以自己选择合适的方法,或者评论下你的做法。
我们下次再见!