49. 字母异位词分组

Posted on 2025-10-13 14:43  lachesism  阅读(11)  评论(0)    收藏  举报
给你一个字符串数组,请你将

组合在一起。可以按任意顺序返回结果列表。

 

示例 1:

输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]

输出: [["bat"],["nat","tan"],["ate","eat","tea"]]

解释:

  • 在 strs 中没有字符串可以通过重新排列来形成 "bat"
  • 字符串 "nat""tan" 是字母异位词,因为它们可以重新排列以形成彼此。
  • 字符串 "ate" ,"eat" 和 "tea" 是字母异位词,因为它们可以重新排列以形成彼此。

示例 2:

输入: strs = [""]

输出: [[""]]

示例 3:

输入: strs = ["a"]

输出: [["a"]]

 

提示:

  • 1 <= strs.length <= 104
  • 0 <= strs[i].length <= 100
  • strs[i] 仅包含小写字母

 

方法一:排序法(推荐)

  1. 创建一个哈希表(Map/Dictionary) 来存储分组结果
  • key:字符串排序后的结果
  • value:原始字符串的列表
  1. 遍历字符串数组,对每个字符串:
  • 将字符串的字符进行排序(例如 "eat" → "aet")
  • 排序后的字符串作为 key
  • 将原始字符串添加到对应 key 的列表中
  1. 为什么这样可行?
  • "eat"、"tea"、"ate" 排序后都是 "aet"
  • 它们会被映射到同一个 key 下
  • 而 "bat" 排序后是 "abt",会有不同的 key
  1. 最后返回哈希表中所有的 value 列表

时间复杂度:O(n × k log k),其中 n 是字符串数量,k 是字符串最大长度(排序需要 k log k)

方法二:字符计数法

  1. 同样用哈希表存储
  1. 对每个字符串统计字符出现次数
  • 例如 "eat" → a:1, e:1, t:1
  • 将这个计数结果转换成一个唯一的标识(比如 "a1e1t1" 或者用数组 [1,0,0,0,1,0,...,1])
  1. 用这个标识作为 key 进行分组

时间复杂度:O(n × k),更快,但实现稍微复杂一点

示例推演

对于 ["eat", "tea", "tan", "ate", "nat", "bat"]:使用排序法:

  • "eat" → 排序 → "aet" → 放入 map["aet"]
  • "tea" → 排序 → "aet" → 放入 map["aet"]
  • "tan" → 排序 → "ant" → 放入 map["ant"]
  • "ate" → 排序 → "aet" → 放入 map["aet"]
  • "nat" → 排序 → "ant" → 放入 map["ant"]
  • "bat" → 排序 → "abt" → 放入 map["abt"]

最后得到:

  • map["aet"] = ["eat", "tea", "ate"]
  • map["ant"] = ["tan", "nat"]
  • map["abt"] = ["bat"]

返回所有的 value 即可。

 

这边选择方法一

 public List<List<String>> groupAnagrams(String[] strs) {
        // 1. 创建哈希表用于分组
        // key: 排序后的字符串(作为"身份证")
        // value: 具有相同"身份证"的原始字符串列表
        Map<String, List<String>> map = new HashMap<>();
        
        // 2. 遍历输入数组中的每个字符串
        for (String str : strs) {
            // 2.1 将字符串转换为字符数组(为了排序)
            char[] charArray = str.toCharArray();
            
            // 2.2 对字符数组进行排序
            // 例如: "eat" -> ['e','a','t'] -> ['a','e','t']
            Arrays.sort(charArray);
            
            // 2.3 将排序后的字符数组转回字符串,作为key
            // 例如: ['a','e','t'] -> "aet"
            String sortedStr = new String(charArray);
            
            // 2.4 在哈希表中查找这个key
            // 如果不存在,就创建一个新的ArrayList
            // 如果存在,就获取已有的列表
            if (!map.containsKey(sortedStr)) {
                map.put(sortedStr, new ArrayList<>());
            }
            
            // 2.5 将原始字符串添加到对应的列表中
            // 例如: "eat" 添加到 map.get("aet") 的列表中
            map.get(sortedStr).add(str);
        }
        
        // 3. 将哈希表中所有的value(列表)提取出来,组成最终结果
        // new ArrayList<>(map.values()) 会创建一个新的列表,包含所有的分组
        return new ArrayList<>(map.values());
    }
    

 今天学到两个知识
1.Arrays.sort() 的工作原理

Arrays.sort(charArray) 会对整个字符数组进行完整的排序,不是部分排序。会保证整个数组从小到大完全有序。

2.Map 的结构

 Map 的每个 value 本身就是一个 List(列表)

因为是Map<String,List<String>> map =new HashMap<>() 所以能输出一组一组的

// 初始化空 map
Map<String, List<String>> map = new HashMap<>();

// ====== 处理 "eat" ======
String str = "eat";
String sortedStr = "aet";  // 排序后

// 此时 map 中没有 "aet" 这个 key
if (!map.containsKey("aet")) {
    // 创建一个新的 ArrayList,并放入 map
    map.put("aet", new ArrayList<>());
}
// 现在 map = {"aet": []}  ← 空列表

// 把 "eat" 添加到这个列表中
map.get("aet").add("eat");
// 现在 map = {"aet": ["eat"]}


// ====== 处理 "tea" ======
String str = "tea";
String sortedStr = "aet";  // 排序后还是 "aet"

// map 中已经有 "aet" 了,跳过 if
// 直接获取 "aet" 对应的列表,然后添加 "tea"
map.get("aet").add("tea");
// 现在 map = {"aet": ["eat", "tea"]}
//                      ↑ 同一个列表对象


// ====== 处理 "ate" ======
String str = "ate";
String sortedStr = "aet";  // 还是 "aet"

map.get("aet").add("ate");
// 现在 map = {"aet": ["eat", "tea", "ate"]}
//                      ↑ 还是同一个列表对象


// ====== 处理 "bat" ======
String str = "bat";
String sortedStr = "abt";  // 新的 key

// map 中没有 "abt"
map.put("abt", new ArrayList<>());
map.get("abt").add("bat");
// 现在 map = {
//   "aet": ["eat", "tea", "ate"],
//   "abt": ["bat"]
// }


// ====== 处理 "tan" ======
String str = "tan";
String sortedStr = "ant";  // 又一个新 key

map.put("ant", new ArrayList<>());
map.get("ant").add("tan");
// 现在 map = {
//   "aet": ["eat", "tea", "ate"],
//   "abt": ["bat"],
//   "ant": ["tan"]
// }


// ====== 处理 "nat" ======
String str = "nat";
String sortedStr = "ant";  // 已存在的 key

map.get("ant").add("nat");
// 最终 map = {
//   "aet": ["eat", "tea", "ate"],
//   "abt": ["bat"],
//   "ant": ["tan", "nat"]
// }

 

Map 内部:
┌─────────────────────────────────────────┐
│  Key        Value                       │
│  ────        ─────                      │
│  "aet"  →  ["eat", "tea", "ate"]       │
│  "abt"  →  ["bat"]                     │
│  "ant"  →  ["tan", "nat"]              │
└─────────────────────────────────────────┘

map.values() 提取所有 value:
┌─────────────────────────┐
│ ["eat", "tea", "ate"]  │
│ ["bat"]                 │
│ ["tan", "nat"]          │
└─────────────────────────┘

new ArrayList<>(map.values()) 包装成大列表:
┌────────────────────────────────────┐
│  List<List<String>>                │
│  ──────────────────────            │
│  [0] → ["eat", "tea", "ate"]      │
│  [1] → ["bat"]                     │
│  [2] → ["tan", "nat"]              │
└────────────────────────────────────┘