字母异位词分组(49. 字母异位词分组 && 剑指 Offer II 033. 变位词组)
题目:
思路:
【1】首先遍历是必不可少的,其次Map的辅助空间也是要用的,那么剩下的便是将字符串转成字符数组,然后排序,再转回字符串,用相同字符串作为key存于Map中,便可以是一组数据,然后遍历Map返回,时间复杂度为O(N*Nlog(N))
【2】换一种思路,字符源于ASCLL码,本质上是有容量限制的,基于题目限制了是小写字符,那么可以考虑用容量为26的数组,遍历一次,记录个数,再将数组转化一下作为key便可以达到和上面一样的效果。将Nlog(N)变为了N。
【3】又基于上面的思路其实还可以用进制的思想,去掉上面的辅助空间,也不用转化了,采用字符*26的形式,累乘便可以得到key,需要处理的就是在数值范围里面会变成0的情况,其次还要处理4*26*26*26 = 8*26*13*26,这种情况,所以又要加上额外的数值进行处理即可。
代码展示:
简单的第一版:
//执行用时:9 ms, 在所有 Java 提交中击败了32.04%的用户 //内存消耗:44.8 MB, 在所有 Java 提交中击败了26.05%的用户 class Solution { public List<List<String>> groupAnagrams(String[] strs) { List<List<String>> result = new ArrayList<>(); HashMap<String, List<String>> stringListHashMap = new HashMap<>(); for (int i = 0; i < strs.length; i++){ String temp = strs[i]; char[] chars = temp.toCharArray(); Arrays.sort(chars); temp = Arrays.toString(chars); List<String> arr = stringListHashMap.get(temp); if (arr == null){ ArrayList<String> arrayList = new ArrayList<>(); arrayList.add(strs[i]); stringListHashMap.put(temp,arrayList); }else { arr.add(strs[i]); } } for (List<String> value:stringListHashMap.values()){ result.add(value); } return result; } }
将第一版的繁琐代码简化:
class Solution { public List<List<String>> groupAnagrams(String[] strs) { HashMap<String, List<String>> hashMap = new HashMap<>(); String temp; for (String str: strs){ char[] chars = str.toCharArray(); Arrays.sort(chars); temp = Arrays.toString(chars); List<String> arr = hashMap.getOrDefault(temp,new ArrayList<String>()); arr.add(str); hashMap.put(temp,arr); } return new ArrayList<List<String>>(hashMap.values()); } }
优化版本:
//执行用时:5 ms, 在所有 Java 提交中击败了98.92%的用户 //内存消耗:44.8 MB, 在所有 Java 提交中击败了28.05%的用户 class Solution { public List<List<String>> groupAnagrams(String[] strs) { HashMap<String, List<String>> hashMap = new HashMap<>(); String temp; for (String str: strs){ char[] ch = new char[26]; for (char c : str.toCharArray()){ ch[c-'a']++; } temp = String.valueOf(ch); List<String> arr = hashMap.getOrDefault(temp,new ArrayList<String>()); arr.add(str); hashMap.put(temp,arr); } return new ArrayList<List<String>>(hashMap.values()); } }
最优解:
//执行用时:4 ms, 在所有 Java 提交中击败了99.94%的用户 //内存消耗:44.6 MB, 在所有 Java 提交中击败了43.26%的用户 class Solution { /** * 利用质数的概念 * 利用数值相乘来作为键值区别两个字符串的不同 * @param strs * @return */ int[] prime = new int[]{3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101 , 103 ,107 , 109 ,113}; public List<List<String>> groupAnagrams(String[] strs) { HashMap<Long, List<String>> hashMap = new HashMap<>(); long temp; for (String str: strs){ temp = 1; for (char c : str.toCharArray()){ // 其实核心是这里面的逻辑 // 1) temp *= (c * 26 + 1); 与 2)temp *= prime[c - 'a']; 本质上是没有区别的 // 为什么呢,2中你用质数,确保每个数值不相同 ,单1中你每加一个字符我都会*26代表多一个字符啊 // 所以本质上都是可行的,至于为什么 c * 26 + 1 ,其实就是和 prime 不能用 1和2是一个道理 // 用 1 ,无限a的结果和单个a的结果一样都是1区分不开 , // 而prime 不能用 2 的理由 和 c * 26 + 1 为什么要加 1 一样 // 为了防止 long 类型溢出的某种情况会转变为 0 ,如果long大到一定程度会变为 4611686018427387904L; // 而此时无论是乘以 2 ,4 ,8 这种只要才分开没有奇数因子的都会变为 0 ,多么搞事的东西,会让逻辑变得不正常 // 所以为了避免为0 才需要 + 1 temp *= (c * 26 + 1); // temp *= prime[c - 'a']; } List<String> arr = hashMap.getOrDefault(temp,new ArrayList<String>()); arr.add(str); hashMap.put(temp,arr); } return new ArrayList<List<String>>(hashMap.values()); } }