LeetCode第49题思悟——字母异位词分组(group-anagrams)

LeetCode第49题思悟——字母异位词分组(group-anagrams)

知识点预告

  1. 字符串特征值的构建;
  2. 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的花销并不小;

知识点小结

  1. 字符串特征值的构建;
  2. HashMap的分类作用;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值