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

简介: 面试题 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{}]是初始化捕获列表,也就是说定义了一个auto fn = hash{};供后续使用

默认是使用hash 来实现的,但是没有办法去实现一个array,因此需要手动去构造一个哈希函数。本次构造哈希函数,是基于已有的hash去实现的,因此哈希碰撞概率几乎为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;
    }
};


相关文章
|
3月前
|
监控 NoSQL Java
面试官:实际工作中哪里用到了自定义注解?
面试官:实际工作中哪里用到了自定义注解?
52 2
|
9月前
|
Java 数据库 数据安全/隐私保护
【Java面试】谈谈你对自定义类加载器的理解
【Java面试】谈谈你对自定义类加载器的理解
95 0
|
11月前
|
SQL Java 数据库连接
Java 最常见的面试题:mybatis 如何编写一个自定义插件?
Java 最常见的面试题:mybatis 如何编写一个自定义插件?
184 0
|
算法 Java 调度
面试八股文:你写过自定义任务调度器吗?
Task.Factory提供了自定义选项、自定义调度器的能力,这也说明了Task.Run是Task.Factory.StartNew的一个特例,Task.Run 只是提供了一个无参、默认的任务创建和调度方式。
面试八股文:你写过自定义任务调度器吗?
|
1月前
|
Java 程序员
java线程池讲解面试
java线程池讲解面试
62 1
|
2月前
|
存储 关系型数据库 MySQL
2024年Java秋招面试必看的 | MySQL调优面试题
随着系统用户量的不断增加,MySQL 索引的重要性不言而喻,对于后端工程师,只有在了解索引及其优化的规则,并应用于实际工作中后,才能不断的提升系统性能,开发出高性能、高并发和高可用的系统。 今天小编首先会跟大家分享一下MySQL 索引中的各种概念,然后介绍优化索引的若干条规则,最后利用这些规则,针对面试中常考的知识点,做详细的实例分析。
253 0
2024年Java秋招面试必看的 | MySQL调优面试题
|
2月前
|
存储 算法 Java
铁子,你还记得这些吗----Java基础【拓展面试常问题型】
铁子,你还记得这些吗----Java基础【拓展面试常问题型】
47 1
|
2月前
|
NoSQL Java 关系型数据库
凭借Java开发进阶面试秘籍(核心版)逆流而上
最近参加了面试或者身边有朋友在面试的兄弟有没有发现,现在的面试不仅会问八股文,还会考察框架、项目实战、算法数据结构等等,需要准备的越来越多。 其实面试的时候,并不是要求你所有的知识点都会,而是关键的问题答到点子上!这份《Java 开发进阶面试秘籍(核心版)》由 P8 面试官整体把控,目前已经更新了 30 万字! 资料中涵盖了一线大厂、中小厂面试真题,毕竟真题都是技术领域最经典的基础知识和经验沉淀的汇总,非常有必要学习掌握!双重 buff 叠加,offer 接到手软~ 点击此处取,这可能是你到目前为止领取的最具含金量的一份资料! 整套资料涵盖:Spring、Spring
|
2月前
|
存储 缓存 Java
面试官:什么是Java内存模型?
面试官:什么是Java内存模型?
113 0
面试官:什么是Java内存模型?
|
1月前
|
消息中间件 NoSQL 网络协议
Java面试知识点复习​_kaic
Java面试知识点复习​_kaic