# 「力扣」第 49 题:字母异位词分组(哈希表)

# 视频讲解

这道题在 B 站 (opens new window) 可以收看视频讲解,选择快速播放,获得更好的观看体验。

# 题目描述

给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。

示例:

输入: ["eat", "tea", "tan", "ate", "nat", "bat"]。
输出:[["ate","eat","tea"],["nat","tan"],["bat"]]。
说明:1、所有输入均为小写字母。2、不考虑答案输出的顺序。

参考代码

class Solution:
    def groupAnagrams(self, strs):
        map = dict()

        if len(strs) == 0:
            return []

        for s in strs:
            key = ''.join(sorted(list(s)))
            if key not in map:
                map[key] = [s]
            else:
                map[key].append(s)

        return list(map.values())

# 思路分析

  • 每个字符串其实对应一个 key,相同字母组合对应的 key 是相等的。这里考察了哈希函数。

Java 代码:

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

public class Solution {

    public List<List<String>> groupAnagrams(String[] strs) {

        // 考察了哈希函数的基本知识,只要 26 个即可
        // (小写字母ACSII 码 - 97 )以后和质数的对应规则,这个数组的元素顺序无所谓
        // key 是下标,value 就是数值
        int[] primes = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29,
                31, 37, 41, 43, 47, 53, 59, 61, 67, 71,
                73, 79, 83, 89, 97, 101};

        // key 是字符串自定义规则下的哈希值
        Map<Integer, List<String>> hashMap = new HashMap<>();
        for (String s : strs) {
            int hashValue = 1;

            char[] charArray = s.toCharArray();
            for (char c : charArray) {
                hashValue *= primes[c - 'a'];
            }


            if (hashMap.containsKey(hashValue)) {
                List<String> curList = hashMap.get(hashValue);
                curList.add(s);
            } else {
                List<String> newList = new ArrayList<>();
                newList.add(s);

                hashMap.put(hashValue, newList);
            }
        }
        return new ArrayList<>(hashMap.values());
    }

    public static void main(String[] args) {
        String[] strs = {"eat", "tea", "tan", "ate", "nat", "bat"};

        Solution solution = new Solution();
        List<List<String>> res = solution.groupAnagrams(strs);
        System.out.println(res);

        System.out.println((int) 'a');
    }

}

Python 代码:

class Solution:
    def groupAnagrams(self, strs):
        """
        :type strs: List[str]
        :rtype: List[List[str]]
        """
        # 26 个质数
        primes = [2, 3, 5, 7, 11,
                  13, 17, 19, 23, 29,
                  31, 37, 41, 43, 47,
                  53, 59, 61, 67, 71,
                  73, 79, 83, 89, 97,
                  101]

        # hash_arr 中存放的是与 res 对应的 hash 值
        hash_arr = []
        res = []

        for s in strs:
            hash_val = 1
            for alpha in s:
                hash_val *= primes[ord(alpha) - ord('a')]
            index = 0
            while index < len(hash_arr):
                if hash_arr[index] == hash_val:
                    res[index].append(s)
                    break
                index += 1
            if index == len(hash_arr):
                hash_arr.append(hash_val)
                res.append([s])
        return res


if __name__ == '__main__':
    strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
    solution = Solution()
    result = solution.groupAnagrams(strs)
    print(result)

Python 代码:

class Solution:
    def groupAnagrams(self, strs):
        """
        :type strs: List[str]
        :rtype: List[List[str]]
        """
        map = dict()

        if len(strs) == 0:
            return []

        for s in strs:
            key = ''.join(sorted(list(s)))
            if key not in map:
                map[key] = [s]
            else:
                map[key].append(s)

        return list(map.values())

这篇题解不是很严谨,可以这么做是我尝试出来的,限于本人对这个知识点的理解有限,思路没有办法说得很清楚,也没有办法证明给大家看,只能说是思路通过测试得到验证。

大家直接看代码,或者看这个题解评论区置顶的评论,有朋友提到证明部分。

有哈希表的基础知识,是很容易想到思路的。这里「哈希」是 hash 的音译,意译是「散列」。

思路

  • 分入一组的字符串的特征:字符以及字符的个数相等,但是顺序不同;
  • 这样的特征其实可以做一个映射,思想来源于哈希规则。这里要去除顺序的影响,那么我们就只关心每个字符以及它出现的次数;
  • 每个字符对应一个 ASCII 值,用 ASCII 值乘以字符出现的次数的和感觉上就能表征一组字符串,但是很容易想到,这里面会有重复的值;
  • 一个替代的做法是,把 ASCII 值 替换成为质数,于是这些数值一定不会有公约数,不在一组的数,它们的和一定不相等(也就是放在哈希表里,肯定不会被分在一个桶里);
  • 所有输入均为小写字母,因此只需要做 26 个映射,这种映射可以通过数组实现。

评论有朋友提到,这样计算出来的「哈希值」是有可能整型越界的,这一点是我一开始没有想到的。但是仔细算了一下,这里「消耗」最大的值就是字母 z ,它对应的 ASCII 码是 25(已经减去了偏移),它对应的质数最大是 101,如果全部使用 z 是最消耗值的,运行下面这段代码:

System.out.println(Integer.MAX_VALUE / 101 / 25);

输出:850488。

因此,要产生溢出,输入字符至少长度要达到 850488 才可以。在这里认为不存在这种测试用例。

知识点复习:

  • 哈希表的底层就是数组;
  • 哈希函数;
  • 哈希冲突的解决办法:1、链接法;2、开放地址法;
  • 哈希表的扩容。

参考《算法导论》或者《算法 4》,初学的时候懂得意思,有个感性认知即可。

参考代码

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Solution {

    public List<List<String>> groupAnagrams(String[] strs) {
        // 考察了哈希函数的基本知识,只要 26 个即可
        // (小写字母ACSII 码 - 97 )以后和质数的对应规则,这个数组的元素顺序无所谓
        // key 是下标,value 就是数值
        int[] primes = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29,
                31, 37, 41, 43, 47, 53, 59, 61, 67, 71,
                73, 79, 83, 89, 97, 101};

        long mod = 1000000000 + 7;

        // key 是字符串自定义规则下的哈希值
        Map<Long, List<String>> hashMap = new HashMap<>();
        for (String s : strs) {
            long hashValue = 1;

            char[] charArray = s.toCharArray();
            for (char c : charArray) {
                hashValue = ((hashValue % mod) * (primes[c - 'a'] % mod)) % mod;
            }

            if (hashMap.containsKey(hashValue)) {
                List<String> curList = hashMap.get(hashValue);
                curList.add(s);
            } else {
                List<String> newList = new ArrayList<>();
                newList.add(s);

                hashMap.put(hashValue, newList);
            }
        }
        return new ArrayList<>(hashMap.values());
    }
}

作者:liweiwei1419 链接:https://suanfa8.com/hash-table/solutions/0049-group-anagrams 来源:算法吧 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Last Updated: 11/19/2024, 7:59:29 AM