LeetCode第49题思悟——字母异位词分组(group-anagrams)
知识点预告
- 字符串特征值的构建;
- HashMap的分类作用;
题目要求
给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/group-anagrams
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
示例
输入: [“eat”, “tea”, “tan”, “ate”, “nat”, “bat”],
输出:
[
[“ate”,“eat”,“tea”],
[“nat”,“tan”],
[“bat”]
]
说明:所有输入均为小写字母。
不考虑答案输出的顺序。来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/group-anagrams
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
我的思路
这道题实际上是在设计一种映射规则:字符串中出现的字母及其数量相同的字符串被划分在一起;
一种比较简单的就是统计每个字符串中字母出现的情况,然后将其按照“字母数字”的形式统一为一个字符串,作为groupId,使用HashMap完成分类聚集的作用;
这是我的原始思路实现:
public List<List<String>> groupAnagrams(String[] strs) {
List<List<String>> results=new ArrayList<>();
List<String> group;
int flags;
int[][] info=new int[29][strs.length];
char[] chars;
int strIndex=0;
int charIndex;
int groupId;
for(String s:strs){
flags=0;
chars=s.toCharArray();
for(char c:chars){
charIndex=(c-'a');
flags|=(1<<charIndex);
info[charIndex+3][strIndex]+=1;
}
info[0][strIndex]=flags;
info[1][strIndex]= chars.length;
if((groupId=appearedPosition(strIndex,info))!=-1){
group=results.get(groupId);
group.add(s);
}else{
group=new ArrayList<>();
group.add(s);
results.add(group);
info[2][strIndex]=results.size()-1;
}
strIndex++;
}
return results;
}
public int appearedPosition(int strIndex,int[][] info){
int flag=info[0][strIndex];
int length=info[1][strIndex];
int charIndex;
for(int i=0;i<strIndex;i++){
if(info[0][i]==flag&&info[1][i]==length){
charIndex=3;
while(charIndex<29&&info[charIndex][i]==info[charIndex][strIndex]){
charIndex++;
}
if(charIndex==29){
return info[2][i];
}
}
}
return -1;
}
提交结果:
可以看出,我这里使用到的标记变量是有一点冗余的:flag和字符出现的情况;flag表示某个字符是否出现过;而字符出现的情况则表示了出现的数量;当然,这点冗余在后面的字符串对比中也是有性能贡献的,比如flag不同,那么这两个字符串一定就不是一类了;至于作用多少就取决于数据集的特征了;
当然,该实现最大的问题在于appearedPosition中确定某一字符串所处分类的实现:这里采用了for循环;意味着每确定一个字符串所在分类,都需要和其前面的字符串做对比:这和冒泡排序是同样的处理思路;那么问题所在也是一致的:重复的计算量;
一种解决方法就是使用HashMap数据结构,以特征字符串为key,以对应的List为value:
public List<List<String>> groupAnagrams(String[] strs){
List<List<String>> result=new ArrayList<>();
HashMap<String,List<String>> container=new HashMap<>();
int[] counter=new int[26];
StringBuilder builder=new StringBuilder();
String key;
List<String> value;
char[] currentStr;
for(String s:strs){
currentStr=s.toCharArray();
for(char c:currentStr){
counter[c-'a']++;
}
for(int i=0;i<26;i++){
builder.append('a'+i);
builder.append(counter[i]);
}
key=builder.toString();
if(container.containsKey(key)){
value=container.get(key);
}else{
value=new ArrayList<>();
container.put(key,value);
}
value.add(s);
builder.delete(0,builder.length());
Arrays.fill(counter,0);
}
Set<Map.Entry<String,List<String>>> entrySet=container.entrySet();
for(Map.Entry<String,List<String>> entry:entrySet){
result.add(entry.getValue());
}
return result;
}
提交结果:
有提高,但是效率似乎还有提高空间,让我们看看优秀解法吧;
优秀解法
//解法A
public List<List<String>> groupAnagrams(String[] strs) {
/* //List<List<String>> result = new ArrayList();
Map<String,List> map = new HashMap();
for(int i = 0;i<strs.length;++i)
{
char[] strchar = strs[i].toCharArray();
Arrays.sort(strchar);
String strString = String.valueOf(strchar);
if(!map.containsKey(strString))
map.put(strString,new ArrayList());
map.get(strString).add(strs[i]);
}
return new ArrayList(map.values());*/
int[] num = { 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 };
Map<Integer,List> map = new HashMap();
for(int i = 0;i<strs.length;++i)
{
String ele = strs[i];
int result = 1;
for(int j = 0;j<ele.length();++j)
{
char ele2 = ele.charAt(j);
result*=num[ele2-'a'];
}
if(!map.containsKey(result))
map.put(result,new ArrayList());
map.get(result).add(strs[i]);
}
return new ArrayList(map.values());
}
//解法B
public List<List<String>> groupAnagrams(String[] strs) {
List<List<String>> res =new ArrayList<>();
if(strs == null||strs.length==0)return res;
HashMap<String, List<String>> map = new HashMap<>();
for(int i=0;i<strs.length;i++){
char[] s = strs[i].toCharArray();
Arrays.sort(s);
String s_sorted = String.valueOf(s);
if(map.containsKey(s_sorted)){
map.get(s_sorted).add(strs[i]);
}else {
List<String> tmp = new ArrayList<>();
tmp.add(strs[i]);
map.put(s_sorted,tmp);
}
}
return new ArrayList<>(map.values());
}
差异分析
很明显啦,差别在于特征值的构建:自己的解法中使用了统计字母出现次数这一很直观的特征值;而解法A则使用了排序后的字符串作为特征值;解法B则使用了质数的积作为特征值;
字符串构造是存在花销的;排序算法时间复杂度是n*log(n);而我得到特征值虽然只需要遍历两遍数组,但是很明显,操作StringBuilder的花销并不小;
知识点小结
- 字符串特征值的构建;
- HashMap的分类作用;