字母异位词分组

解法 1:先排序,再 hash

优点:简单,易于实施。

使用 lambda 表达式解题:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        return Arrays.stream(strs)
                .collect(Collectors.groupingBy(s -> {
                    char [] ch = s.toCharArray();
                    Arrays.sort(ch);
                    return new String(ch);
                }))
                .values()
                .stream()
                .collect(Collectors.toList());
    }
}

更加简化写法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        Map<String, List<String>> map = new HashMap<>();
        for (String str : strs) {
            char[] ch = str.toCharArray();
            Arrays.sort(ch);
            map.computeIfAbsent(String.valueOf(ch), k -> new ArrayList<>()).add(str);
        }
        return new ArrayList<>(map.values());
    }
}

当 Map 里没有某个 key 时,就用你传入的 lambda 生成一个默认值放进去,并返回这个值;如果 key 已存在,就直接返回已有值。它非常适合“分组/聚合”这类场景。

Go 语言写法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
func groupAnagrams(strs []string) [][]string {
    m := make(map[string][]string, len(strs))

    for _, s := range strs {
        b := []byte(s)
        sort.Slice(b, func(i, j int) bool {return b[i] < b[j]})
        key := string(b)
        m[key] = append(m[key], s)
    }

    res := make([][]string, 0, len(m))
    for _, v := range m {
        res = append(res, v)
    }
    return res
}

Python 写法

1
2
3
4
5
6
7
8
9
class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        mp = defaultdict(list)

        for s in strs:
            key = ''.join(sorted(s))          # 通用:排序作为 key
            mp[key].append(s)

        return list(mp.values())
  • defaultdict 会创建一个默认的 dict,在访问某个 value 不存在的 key 时,会自动调用传入的工厂函数来设置 value 值。

JS 写法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
/**
 * LeetCode 49 - Group Anagrams
 * 通用写法:排序后字符串作为 key
 * 时间复杂度:O(n * k log k)(k 为单词平均长度)
 * 空间复杂度:O(n * k)
 *
 * @param {string[]} strs
 * @return {string[][]}
 */
function groupAnagrams(strs) {
  const map = new Map(); // key -> string[]

  for (const s of strs) {
    const key = s.split("").sort().join(""); // 规范化 key
    if (!map.has(key)) map.set(key, []);
    map.get(key).push(s);
  }

  return Array.from(map.values());
}

O(n*k) 复杂度写法

Java 写法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        Map<String, List<String>> map = new HashMap<>(strs.length * 2);

        for (String s : strs) {
            int[] cnt = new int[26];
            for (int i = 0; i < s.length(); i++) {
                cnt[s.charAt(i) - 'a']++;
            }

            // 用计数向量编码成 key:#1#0#0...(避免歧义)
            StringBuilder sb = new StringBuilder(26 * 2);
            for (int i = 0; i < 26; i++) {
                sb.append('#').append(cnt[i]);
            }
            String key = sb.toString();

            map.computeIfAbsent(key, k -> new ArrayList<>()).add(s);
        }

        return new ArrayList<>(map.values());
    }
}

更加简洁的写法是,可以直接生成 array 的 hashcode,虽然有概率哈希冲突。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        Map<Integer, List<String>> map = new HashMap<>(strs.length * 2);

        for (String s : strs) {
            int[] cnt = new int[26];
            for (int i = 0; i < s.length(); i++) {
                cnt[s.charAt(i) - 'a']++;
            }
            Integer key = Arrays.hashCode(cnt);
            map.computeIfAbsent(key, k -> new ArrayList<>()).add(s);
        }

        return new ArrayList<>(map.values());
    }
}

同时,我们甚至能直接使用 list 作为 key:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        Map<List<Integer>, List<String>> map = new HashMap<>(strs.length * 2);

        for (String s : strs) {
            Integer[] cnt = new Integer[26];
            Arrays.fill(cnt, 0);
            for (int i = 0; i < s.length(); i++) {
                cnt[s.charAt(i) - 'a']++;
            }
            map.computeIfAbsent(new ArrayList(Arrays.asList(cnt)), k -> new ArrayList<>()).add(s);
        }

        return new ArrayList<>(map.values());
    }
}

Go 写法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
func groupAnagrams(strs []string) [][]string {
    mp := make(map[[26]byte][]string)

    for _, s := range strs {
        var key [26]byte
        for i := 0; i < len(s); i ++ {
            key[s[i] - 'a'] ++
        }
        mp[key] = append(mp[key], s)
    }

    res := make([][]string, 0)
    for _, v := range mp {
        res = append(res, v)
    }
    return res
}

Go 语言还可以这么写:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
func groupAnagrams(strs []string) [][]string {
    mp := make(map[string][]string)

    for _, s := range strs {
        var key [26]byte
        for i := 0; i < len(s); i ++ {
            key[s[i] - 'a'] ++
        }
        keyStr := string(key)
        mp[keyStr] = append(mp[keyStr], s)
    }

    res := make([][]string, 0)
    for _, v := range mp {
        res = append(res, v)
    }
    return res
}

Python 写法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        mp = defaultdict(list)

        for s in strs:
            cnt = [0] * 26
            for ch in s:
                cnt[ord(ch) - ord('a')] += 1

            mp[tuple(cnt)].append(s)  # O(1) 哈希 key(按 26 维计数)

        return list(mp.values())

python 写法中需要注意的是,字符转整数需要通过 ord 转,不能直接进行计算。

tuple 可以作为 map 的 key,其他不行。

JS 写法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
function groupAnagrams(strs) {
  const map = new Map(); // key -> string[]

  for (const s of strs) {
    const cnt = new Array(26).fill(0);
    for (let i = 0; i < s.length; i++) {
      cnt[s.charCodeAt(i) - 97]++; // 'a' 的 ASCII/Unicode 码点是 97
    }

    // 计数数组转 key(加分隔符避免歧义)
    const key = cnt.join("#");

    if (!map.has(key)) map.set(key, []);
    map.get(key).push(s);
  }

  return Array.from(map.values());
}

需要注意的是,js 中,charAt 返回的是字符串,而 charCodeAt 才返回整数。