初次尝试将字符串转成列表再转成集合进行比较,这样会去重,测试用例[[‘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 中提取所有的值,并将这些值作为一个列表返回,返回结果即为题目中需要的结果。
方法二:计数
- 由于互为字母异位词的两个字符串包含的字母相同,因此对两个字符串分别进行排序之后得到的字符串一定是相同的,故可以将排序之后的字符串作为哈希表的键。
- 双层循环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;
}
}
问题
- groupAnagrams方法报错:
This method must return a result of type List<List<String>>
因为逻辑有问题,可能不会执行到最后的return
语句。
原因在于j++
只在if
条件满足时递增,这样可能会导致j
永远不会递增(如果isAnagram
返回false
),从而进入死循环,所以要将j++
从if
中移出。 while
循环条件错误,导致逻辑问题,正确的逻辑应该是如果flag=1,跳过这个元素遍历后面的元素,但如果在循环条件中判断flag!=1
,可能会导致没有遍历到所有的元素,while就直接退出了。
修改方案
- 可以将
j
的初始值改为i
,减少一定的时间复杂度 - 修改
while(j < strs.length && flag[i] != 1)
中flag[i]
为flag[j]
- 可以减少使用
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;
}
}
问题
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"});
- 超时,是因为
if(flag[i] == 1) {
continue;
}
中跳过时需要i++
,否则会出现死循环。
3. 修改后,测试用例通过了,但耗时太长
注:
- 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如何体现哈希表的基本思想。