1我的错误解法
public static List<List<String>> m1(String[] strs) {
//暴力解法
//用来记录已经加入结果的字符串,避免结果重复
List<String> count = new ArrayList<>();
List<List<String>> result = new ArrayList<>();
for (int i = 0; i < strs.length ; i++) {
if(count.contains(strs[i])){
//已经加入过结果集合,跳出此次遍历
continue;
}
//暂时集合,用来存储与str[i]互为字母异位词的字符串
ArrayList<String> tamp = new ArrayList<>();
tamp.add(strs[i]);
for (int j = i+1; j < strs.length ; j++) {
boolean flag = true;
//遍历除了str[i]的其他字符串
if(strs[i].length()!=strs[j].length()) {
//长度不同,直接下一次循环
continue;
}
for (byte aByte : strs[i].getBytes()) {
if(!strs[j].contains(String.valueOf((char)aByte))){
//如果有一个字符不同,则可判断不是互为字母异位词
flag=false;
break;
}
}
if(flag){
//此时通过长度和字符检验,互为字母异位词
tamp.add(strs[j]);
count.add(strs[j]);
}
}
//将本次遍历结果加入结果集合
result.add(tamp);
}
return result;
}
错误分析:
- 仅检查
strs[i]的字符是否在strs[j]中出现,未检查字符出现次数是否相同(如"aab"和"abb"会误判)。 - 未验证
strs[j]的字符是否都在strs[i]中(双向包含才成立)。
1.1ds修正方法
import java.util.*;
public class GroupAnagrams {
/**
* 将字符串数组中的字母异位词分组
* @param strs 输入字符串数组
* @return 分组的字母异位词列表
*/
public static List<List<String>> groupAnagrams(String[] strs) {
// 结果列表,存放分好组的字母异位词列表
List<List<String>> result = new ArrayList<>();
// 记录字符串是否已被分组,防止重复处理
boolean[] visited = new boolean[strs.length];
// 1. 遍历数组中的每个字符串
for (int i = 0; i < strs.length; i++) {
// 1.1 跳过已处理的字符串
if (visited[i]) continue;
// 1.2 创建新分组,包含当前字符串
List<String> group = new ArrayList<>();
group.add(strs[i]);
visited[i] = true; // 标记为已访问
// 2. 与当前字符串后面的所有字符串比较
for (int j = i + 1; j < strs.length; j++) {
// 2.1 跳过已处理的字符串
if (visited[j]) continue;
// 2.2 检查是否是字母异位词
if (isAnagram(strs[i], strs[j])) {
group.add(strs[j]); // 添加到当前分组
visited[j] = true; // 标记为已访问
}
}
// 3. 将当前分组添加到结果列表
result.add(group);
}
return result;
}
/**
* 判断两个字符串是否为字母异位词
* 更优方法:使用字符计数代替排序
* @param s 第一个字符串
* @param t 第二个字符串
* @return 如果是字母异位词返回true,否则false
*/
private static boolean isAnagram(String s, String t) {
// 1. 长度不同直接排除
if (s.length() != t.length()) return false;
// 2. 创建字母计数器(26个小写英文字母)
int[] charCount = new int[26];
// 3. 计算s中每个字符出现的次数
for (char c : s.toCharArray()) {
charCount[c - 'a']++;
}
// 4. 减去t中每个字符出现的次数
for (char c : t.toCharArray()) {
int index = c - 'a';
charCount[index]--;
// 5. 如果某个字符出现次数为负,直接返回false
if (charCount[index] < 0) {
return false;
}
}
return true;
}
public static void main(String[] args) {
// 测试用例
String[] strs = {"eat", "tea", "tan", "ate", "nat", "bat"};
List<List<String>> result = groupAnagrams(strs);
// 打印结果
System.out.println("输入: " + Arrays.toString(strs));
System.out.println("分组结果:");
for (List<String> group : result) {
System.out.println(group);
}
/* 输出:
分组结果:
[eat, tea, ate]
[tan, nat]
[bat]
*/
}
}
1.2总结
在暴力解法中有以下注意事项:
1.排除已经处理过的字符串,防止重复记录(我和ds均采用新建数组/集合记录处理过的字符串)
2.判断字母异位词:
在我的方法中,采用判断字符串中每个字符都被另一个字符串包含且两字符串长度相等来判断是否为字母异位词,该方法存在严重漏洞,详见上。关于此,ds提供了两种方法
方法1
private static boolean isAnagram(String s, String t) {
// 1. 长度不同则直接排除
if (s.length() != t.length()) return false;
// 2. 将字符串转为字符数组
char[] sArray = s.toCharArray();
char[] tArray = t.toCharArray();
// 3. 对字符数组进行排序
Arrays.sort(sArray);
Arrays.sort(tArray);
// 4. 比较排序后的字符数组是否相同
return Arrays.equals(sArray, tArray);
}
方法2
private static boolean isAnagram(String s, String t) {
// 1. 长度不同直接排除
if (s.length() != t.length()) return false;
// 2. 创建字母计数器(26个小写英文字母)
int[] charCount = new int[26];
// 3. 计算s中每个字符出现的次数
for (char c : s.toCharArray()) {
charCount[c - 'a']++;
}
// 4. 减去t中每个字符出现的次数
for (char c : t.toCharArray()) {
int index = c - 'a';
charCount[index]--;
// 5. 如果某个字符出现次数为负,直接返回false
if (charCount[index] < 0) {
return false;
}
}
其实对于方法二判断字母异位词,也是哈希方法的一种体现
2哈希法
public static List<List<String>> groupAnagrams(String[] strs) {
// 1. 创建哈希映射:键 = 字母计数的特征值,值 = 异位词列表
Map<String, List<String>> map = new HashMap<>();
// 2. 遍历所有字符串
for (String s : strs) {
// 2.1 创建26个元素的计数器(针对小写英文字母)
int[] charCount = new int[26];
// 2.2 计算字符串中每个字符的出现次数
for (char c : s.toCharArray()) {
charCount[c - 'a']++; // 字符'a'对应索引0,'b'对应索引1...
}
// 2.3 生成唯一的特征键(字符计数签名)
StringBuilder keyBuilder = new StringBuilder();
for (int count : charCount) {
keyBuilder.append(count).append('#'); // 使用#分隔数字
}
String key = keyBuilder.toString();
// 2.4 将当前字符串加入对应分组
// 如果key不存在,创建新分组;否则获取现有分组
map.computeIfAbsent(key, k -> new ArrayList<>()).add(s);
}
// 3. 直接返回Map中所有值组成的列表
return new ArrayList<>(map.values());
}
思路:判断是否为字母异位词与暴力解法中ds给出的方法二相似。整体思路为,创建哈希表(键 = 字母计数的特征值,值 = 异位词列表),遍历整个字符串数组,给定一个长度为26的数组,从索引0-25对应字母a-z,对当前字符串进行遍历,将当前字符对应的数组索引加一(通过该字符减去'a'得到对应索引),当前字符串遍历结束后,数组即为该字符串每个字母出现次数的映射,将数组转为字符串(字母计数特征值key),最后将当前字符串加入哈希表对应分组,如果key不存在,创建新分组;否则获取现有分组,所有字符串遍历结束后直接返回Map中所有值组成的列表
整体思路:【小白都能听懂的算法课】【力扣】【LeetCode 49】 字母异位词分组|哈希表|排序|字符串_哔哩哔哩_bilibili
判断两个字符串互为字母异位词思路:
学透哈希表,数组使用有技巧!Leetcode:242.有效的字母异位词_哔哩哔哩_bilibili