题目
给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。
示例:
输入: ["eat", "tea", "tan", "ate", "nat", "bat"]
输出:
[
["ate","eat","tea"],
["nat","tan"],
["bat"]
]
说明:
所有输入均为小写字母。
不考虑答案输出的顺序。
思路一
- 创建一个boolean数组,长度为给定数组的长度。用来记录数组中字符串是否已经加入了其他异位词类型中
- 遍历整个给定数组,如果这个字符串没有加入其他类型,将它加入到新创建到的数组中,并且从当前开始,与之后的字符串比较,是否为同一异位词, 是的话也加入这个数组中,并且将这个字符串boolean标记为true。
- 写 比较字符串是否为异位词 方法
代码
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
List<List<String>> list = new ArrayList<>();
boolean[] judge = new boolean[strs.length];
for (int i = 0; i < strs.length; i++) {
if(!judge[i]) { //字符串不是其他类型的,没加入其他类型异位词
List<String> temp = new ArrayList<>();
temp.add(strs[i]);
for (int j = i+1; j < strs.length; j++) {
if(judge[j] == false && equals(strs[i] , strs[j])) {
temp.add(strs[j]);
judge[j] = true;
}
}
list.add(temp);
}
}
return list;
}
public boolean equals(String s, String t) { //自己建立的类似哈希表,因为只有26个字母并且小写
if (s.length() != t.length()) {
return false;
}
int[] a = new int[26];
for(int i = 0; i < s.length(); i++) {
a[s.charAt(i) - 'a']++;
a[t.charAt(i) - 'a']--;
}
for (int count : a) {
if(count != 0) {
return false;
}
}
return true;
}
private boolean equals(String string1, String string2) { //这里是用纯hash表,将字符放入hash表中,什么字符都行
Map<Character, Integer> hash = new HashMap<>();
//记录第一个字符串每个字符出现的次数,进行累加
for (int i = 0; i < string1.length(); i++) {
if (hash.containsKey(string1.charAt(i))) {
hash.put(string1.charAt(i), hash.get(string1.charAt(i)) + 1);
} else {
hash.put(string1.charAt(i), 1);
}
}
//记录第一个字符串每个字符出现的次数,将之前的每次减 1
for (int i = 0; i < string2.length(); i++) {
if (hash.containsKey(string2.charAt(i))) {
hash.put(string2.charAt(i), hash.get(string2.charAt(i)) - 1);
} else {
return false;
}
}
//判断每个字符的次数是不是 0 ,不是的话直接返回 false
Set<Character> set = hash.keySet(); //返回一个集合是key的Set
for (char c : set) {
if (hash.get(c) != 0) {
return false;
}
}
return true;
}
}
复杂度分析
时间复杂度:虽然有俩个for循环,但是通过boolean数组标记,每个值都只访问了一遍,所以复杂度为O(N),下面比较函数,O(K),K是字符串长度。 所以,总的时间复杂度O(N*K)
空间复杂度:O(N*K)
思路二
把一类的字符串用某一种方法唯一的映射到同一个位置就可以。 使用hash表。
确认好key和value
key是一种类型的字符串,将字符串中字符排好序,确认好这一类型
value是链表,属于这一类型的字符串都加入
结果直接 new ArrayList<List>(hash.values());
代码
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
HashMap<String, List<String>> hash = new HashMap<>();
for (int i = 0; i < strs.length; i++) {
char[] a = strs[i].toCharArray(); //每个字符串变成数组
Arrays.sort(a); //排好序
String key = String.valueOf(a); //映射成key
if (hash.containsKey(key)) { //如果hash里有这种类型,字符串加入这个链表
hash.get(key).add(strs[i]);
} else { //如果hash表没有这中类型,new一个链表,并加入
List<String> list = new ArrayList<>();
list.add(strs[i]);
hash.put(key, list);
}
}
return new ArrayList<List<String>>(hash.values());
}
}
复杂度分析
时间复杂度:排序的话算作 O(NlogN),最外层的 for 循环,所以就是 O(K*N logN),字符串长度N,给定数组中长度为K,里面有K个字符串。
空间复杂度:O(N*K)
优化
如何去优化,去选择正确的key,去存储对应的value。思路一中是暴力,每个字符串都需要相互比较一下是不是属于同一类型。
解法二中是,通过key来建立类型,放入hash表中。每个字符串都看看自己的类型是不是已经有的还是没有的。
这里的类型是 直接字符串排序看是否相同,其中排序需要耗费时间NloghN。 如何优化呢?
可以设计一种不需要如此时间复杂度的类型,与字符串匹配。每个字符串查看自己类型的时候更快。
优化
-
用一个数组存储质数 prime = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103}。26个
-
然后每个字符串的字符减去 ’ a ’ ,然后取到 prime 中对应的质数。把它们累乘。
-
例如 abc ,就对应 ‘a’ - ‘a’, ‘b’ - ‘a’, ‘c’ - ‘a’,即 0, 1, 2,也就是对应素数 2 3 5,然后相乘 2 * 3 * 5 = 30,就把 “abc” 映射到了 30。
-
这样,这个30是唯一的,只能由‘a’,‘b’,‘c’三个字符才能得到,这就符合题目,属于同一类类型
时间复杂度:O(n∗K),K 是字符串的最长长度。
空间复杂度:O(N*K),用来存储结果。
作者:windliang
来源:力扣(LeetCode)