面试题 10.02:变位词组(自定义哈希函数)

题目

题目链接
编写一种方法,对字符串数组进行排序,将所有变位词组合在一起。变位词是指字母相同,但排列不同的字符串。

示例:

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

解题

方法一:排序

参考链接

class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        unordered_map<string,vector<string>> map;
        for(string& str:strs){
            string key=str;
            sort(key.begin(),key.end());
            map[key].push_back(str);
        }
        vector<vector<string>> res;
        for(auto&[_,v]:map){
            res.push_back(v);
        }
        return res;
    }
};

方法二:哈希表(自定义哈希函数)

参考链接

class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        // 自定义对 array<int, 26> 类型的哈希函数
        auto arrayHash=[fn=hash<int>{}](const array<int,26>& arr)->size_t{
            return accumulate(arr.begin(),arr.end(),0u,[&](size_t acc,int num){
                return (acc<<1)^fn(num);
            });
        };
        unordered_map<array<int,26>,vector<string>,decltype(arrayHash)> map(0,arrayHash);
        for(string& str:strs){
            array<int,26> tmp{};//一定要加括号,才会初始化元素都为0,否则元素是乱的。
            for(char c:str){
                tmp[c-'a']++;
            }
            map[tmp].push_back(str);
        }
        vector<vector<string>> res;
        for(auto it=map.begin();it!=map.end();it++){
            res.push_back(it->second);
        }
        return res;
    }
};

补充
上面定义哈希函数其实是嵌套了两个lambda函数,下面这样看会清晰点

auto arrayHash = [fn = hash<int>{}] (const array<int, 26>& arr) -> size_t                                                                                                                      
{
    return accumulate(arr.begin(), arr.end(), 0u, 
        [&](size_t acc, int num)
        {   
            return (acc << 1) ^ fn(num);
        }); 
};

其中[fn = hash<int>{}]是初始化捕获列表,也就是说定义了一个auto fn = hash<int>{};供后续使用
默认是使用hash<T> 来实现的,但是没有办法去实现一个array,因此需要手动去构造一个哈希函数。本次构造哈希函数,是基于已有的hash<T>去实现的,因此哈希碰撞概率几乎为0,因此无需自定义判断函数。

这是unordered_map的构造,我们可以在unordered_map文档查看定义的模板类型,并在unordered_map构造函数文档查看构造函数的参数。

unordered_map<array<int, 26>, vector<string>, decltype(arrayHash)> map(0, arrayHash);

其他定义哈希函数的方式

1.仿函数
用class实现
主要函数名后面要加const

class arrayHash{
            public:
                size_t operator()(const array<int,26>& arr) const{
                    hash<int> fn;
                    return accumulate(arr.begin(),arr.end(),0u,[&](size_t acc,int num){
                        return (acc<<1)^fn(num);
                    });
                }
        };
unordered_map<array<int,26>,vector<string>,arrayHash> map(0,arrayHash());//arrayHash是一个类,arrayHash()是一个函数名(仿函数)

用struct实现
可以调用无参构造函数,因为传入了类,并且重载了方法。但是之前用lambda函数实现的decltype(…)必须调用有参构造函数,因为传入的只有一个类型,我们还需要对该类型函数进行实现的补充。

struct arrayHash{
            size_t operator()(const array<int,26>& arr) const{
                hash<int> fn;
                return accumulate(arr.begin(),arr.end(),0u,[&](size_t acc,int num){
                    return (acc<<1)^fn(num);
                });
            }
        };
unordered_map<array<int,26>,vector<string>,arrayHash> map;

2.静态函数

定义一个自定义静态函数

static size_t arrayHash(const array<int,26>& arr){
        return accumulate(arr.begin(),arr.end(),0u,[&,fn=hash<int>{}](size_t acc,int num){
                    return (acc<<1)^fn(num);
                });
    }

注意要加取址符号,并获得类型。 注意要调用有参构造函数。

unordered_map<array<int,26>,vector<string>,decltype(&arrayHash)> map(0,arrayHash);

参考链接
C语言中的0U或1U是什么意思?其中0U表示无符号整型0
accumulate用法
C++的STL中accumulate的用法
array用法 array是定长数组,而vector是可变长数组
hash用法
unordered_map官方文档
unordered_map自定义hash键
unordered_map自定义哈希函数和判断函数

方法三:哈希表简洁写法

参考链接
既然unordered_map默认没有 array或者vector作为键,那么转变思路,直接用string来作为哈希键。

class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        vector <vector<string>> ans;
        unordered_map <string, vector<string>> ss;

        for(auto &str : strs) {
            string s = string(26, '0');
            for(auto &c : str) s[c - 'a']++;
            ss[s].emplace_back(str);
        }
        for(auto &c : ss) ans.emplace_back(c.second);

        return ans;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菊头蝙蝠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值