49.字母异位词分组

在这里插入图片描述
初次尝试将字符串转成列表再转成集合进行比较,这样会去重,测试用例[[‘ddddddddddg’, ‘dgggggggggg’]]过不了。
解决办法:改用sorted函数。

一、暴力解法

时间复杂度 O ( n 2 ) O(n^2) O(n2),时间会超限。

class Solution(object):
def groupAnagrams(self, strs):
    """
    :type strs: List[str]
    :rtype: List[List[str]]
    """
    result = []
    flag = [0] * len(strs)
    i = 0
    while i < len(strs):
        # 内外循环都不用找flag=1的元素
        if flag[i] == 1:
            # while循环用continue跳过当前循环需要i+=1,否则会变成死循环
            i += 1
            continue
        str_sorted = sorted(strs[i])
        re = []
        # 全部更新成j
        for j in range(len(strs)):
            if flag[j] == 0 and str_sorted == sorted(strs[j]):
                re.append(strs[j])
                flag[j] = 1
        if re:
            result.append(re)
        i += 1
    return result

解决办法:正如题目标签,用哈希表。

二、哈希表

方法一:排序

基本思想

由于互为字母异位词的两个字符串包含的字母相同,因此对两个字符串分别进行排序之后得到的字符串一定是相同的,故可以将排序之后的字符串作为哈希表的键。

版本一:使用python中的defaultdict

class Solution(object):
def groupAnagrams(self, strs):
    anagrams = defaultdict(list)
    for s in strs:
        # 使用元组作为哈希表的键,元组是不可变的,可以作为字典的键
        # sorted(s) 将每个字符串排序,并转换为元组
        anagrams[tuple(sorted(s))].append(s)
    return anagrams.values()

代码详解:

  • defaultdict
  • 在Python中,defaultdict是一个由collections模块提供的字典子类,它允许你为字典提供一个默认值。当你尝试访问字典中不存在的键时,defaultdict会自动使用该默认值,而不是引发KeyError异常。这使得在处理数据时更加灵活和方便。
  • 使用时导包from collections import defaultdict
  • anagrams[tuple(sorted(s))].append(s)
  • sorted(s):这个表达式将字符串 s 中的所有字母按照字典顺序排序。排序后的结果是一个列表,其中包含了 s 的所有字母,但是以排序后的顺序排列。
  • tuple(sorted(s)):将排序后的列表转换为一个元组。由于字典的键必须是不可变类型,而列表是可变的,因此需要将排序后的列表转换为元组,以便用作字典的键。
  • anagrams[tuple(sorted(s))]:这个表达式访问字典 anagrams 中键为 tuple(sorted(s)) 的条目。由于 anagrams 是一个 defaultdict,如果这个键之前不存在,那么会自动创建一个默认值为空列表的条目。
  • .append(s):这个方法调用将原始字符串 s 添加到上一步获取的列表中。这样,所有字母组成相同(即所有排序后的结果相同的字符串)的字符串都会被添加到同一个列表中,这个列表就是字典中相应键的值。
  • 例:
    ["eat","tea","tan","ate","nat","bat"]
    会转换成
    {('a', 'e', 't'): ['eat', 'tea', 'ate'], ('a', 'n', 't'): ['tan', 'nat'], ('a', 'b', 't'): ['bat']}
  • return anagrams.values()
    这一行代码的作用是从字典 anagrams 中提取所有的值,并将这些值作为一个列表返回,返回结果即为题目中需要的结果。

方法二:计数

  1. 由于互为字母异位词的两个字符串包含的字母相同,因此对两个字符串分别进行排序之后得到的字符串一定是相同的,故可以将排序之后的字符串作为哈希表的键。
  • 双层循环strs,设置标志数组flag
  • 外层循环,i为循环变量,选取flag为零的,如果flag=1则跳过
  • 内层循环,j为循环变量,从当前位置向后遍历,选择flag为零的,并判断是否和strs[i]为异位词,如果是,加入临时数组re,flag置为1
  • 判断两个字符串是否为异位词思路见另一篇文章242.有效的字母异位词

基本思想

由于互为字母异位词的两个字符串包含的字母相同,因此两个字符串中的相同字母出现的次数一定是相同的,故可以将每个字母出现的次数相同,则字符串互为异位词。

版本一

思路:

  • 计数,定义isAnagram函数判断两个字符串是否为异位词
  • 再通过两层循环两两相比,讲互为异位词的字符串放在一起

缺点:
二层循环,且在里层循环中调用isAnagram函数,时间复杂度为 O ( n 3 ) O(n^3) O(n3) ,时间复杂度太高,测试用例可以通过,但耗时太长。

class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
    	List<List<String>> result = new ArrayList<>();
    	if (strs == null || strs.length == 0) {
            return result;
        }
        int[] flag = new int[strs.length];
        int i = 0;
        int j = 0;
        while(i < strs.length && flag[i] != 1){
            String str = strs[i];
            List<String> re = new ArrayList<>();
            while(j < strs.length && flag[i] != 1){
                if(isAnagram(str, strs[j])){
                	flag[j] = 1;
                    re.add(strs[j]);
                    j++;
                }
            result.add(re);
            i++;
        }
        return result;
        }
    }
    public boolean isAnagram(String s, String t) {
        int[] record = new int[26];
        for(int i = 0;i < s.length();i++){
        	record[s.charAt(i)-'a']++;
        };
        for(int i = 0;i < t.length();i++){
        	record[t.charAt(i)-'a']--;
        };
        for(int count:record){
        	if(count != 0){
        		return false;
        	};
        };
        return true;   
    } 
}

问题

  1. groupAnagrams方法报错:This method must return a result of type List<List<String>>
    因为逻辑有问题,可能不会执行到最后的return语句。
    原因在于 j++ 只在 if 条件满足时递增,这样可能会导致 j 永远不会递增(如果 isAnagram 返回 false),从而进入死循环,所以要将j++if中移出。
  2. while循环条件错误,导致逻辑问题,正确的逻辑应该是如果flag=1,跳过这个元素遍历后面的元素,但如果在循环条件中判断flag!=1,可能会导致没有遍历到所有的元素,while就直接退出了。

修改方案

  1. 可以将j的初始值改为i,减少一定的时间复杂度
  2. 修改while(j < strs.length && flag[i] != 1)flag[i]flag[j]
  3. 可以减少使用while循环
class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
    	List<List<String>> result = new ArrayList<>();
    	if (strs == null || strs.length == 0) {
            return result;
        }
        int[] flag = new int[strs.length];
        int i = 0;
        while(i < strs.length){
        	if(flag[i] == 1) {
        		continue;
        	}
            String str = strs[i];
            List<String> re = new ArrayList<>();
            int j = i;
            while(j < strs.length){
                if(flag[j] == 0 && isAnagram(str, strs[j])){
                	flag[j] = 1;
                    re.add(strs[j]);
                }
                j++;
            }
            i++;
            result.add(re);
        }
        return result;
    }
    public boolean isAnagram(String s, String t) {
        int[] record = new int[26];
        for(int i = 0;i < s.length();i++){
        	record[s.charAt(i)-'a']++;
        }
        for(int i = 0;i < t.length();i++){
        	record[t.charAt(i)-'a']--;
        }
        for(int count:record){
        	if(count != 0){
        		return false;
        	}
        }
        return true;   
    } 
}

问题

  1. s.groupAnagrams({"eat","tea","tan","ate","nat","bat"});报错Syntax error, type annotations are illegal here.
    在 Java 中,如果要直接传递一个字符串数组字面量给方法,你需要使用 new String[] 语法。
    修改如下:s.groupAnagrams(new String[]{"eat","tea","tan","ate","nat","bat"});
  2. 超时,是因为
if(flag[i] == 1) {
                continue;
        	}

中跳过时需要i++,否则会出现死循环。
3. 修改后,测试用例通过了,但耗时太长
注:

  1. Java List(列表)
    • 在Java中,List接口是一个有序的集合,它允许我们按顺序存储和访问元素。它扩展了集合接口。
    • 在Java中,必须导入 java.util.List 包才能使用List。
    • Collection接口中还提供了一些常用的List接口方法:
      • add() - 将元素添加到列表
      • addAll() - 将一个列表的所有元素添加到另一个
      • get() - 有助于从列表中随机访问元素
      • iterator() - 返回迭代器对象,该对象可用于顺序访问列表的元素
      • set() - 更改列表的元素
      • remove() - 从列表中删除一个元素
      • removeAll() - 从列表中删除所有元素
      • clear() - 从列表中删除所有元素(比removeAll()效率更高)
      • size() - 返回列表的长度
      • toArray() - 将列表转换为数组
      • contains() - 如果列表包含指定的元素,则返回true

版本二

思路
使用Java中的HashMap,将每个字母出现的次数存放在数组中,连成字符串,作为哈希表的键。
注:
2. HashMap用法
(1)创建
创建一个 HashMap 对象 Sites, 整型(Integer)的 key 和字符串(String)类型的 value:HashMap<Integer, String> Sites = new HashMap<Integer, String>();
(2)getOrDefault() 获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值。语法:hashmap.getOrDefault(Object key, V defaultValue)

启示

  • 系统性学习和练习哈希表类型的算法题。
  • 思考用数组作为哈希表和使用HashMap的区别,Java中的HashMap或者Python中的defaultdict如何体现哈希表的基本思想。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值