LeeCode Hot 100之哈希表篇

1.哈希表

1.1 哈希表的定义

        哈希表就是在关键字和存储位置之间建立对应关系,使得元素的查找可以以O(1)的效率进行, 其中关键字和存储位置之间是通过散列函数建立关系。

        常见的散列函数有如下几种:线性地址法、除数取余法、平方取中法等等。

        而往往这些方法会遇到一些地址冲突的情况,因此还有解决地址冲突的方法。一种思路是开放地址法,将发生冲突的元素映射到其他地址中,这种思路对应的方法有:线性探测法、平方探测法、再散列法等等;另一种思路是思考能否将地址冲突的元素还是就地存储,不映射到其他地址中,这种思路对应的方法有:拉链法。

1.2 C++ STL库中的哈希表

1.2.1 STL库的哈希表基础

通过导入以下头文件,即可创建哈希表

#include<unordered_map>

通过以下方式即可创建一个哈希表

unordered_map<elemType_1, elemType_2> var_name; //声明一个没有任何元素的哈希表,
//其中elemType_1和elemType_2是模板允许定义的类型,如要定义一个键值对都为Int的哈希表:
unordered_map<int, int> map;

//当然也可在初始化时带有元素
unordered_map<int, int> hmap{ {1,10},{2,12},{3,13} };

STL哈希表类似于其他的STL容器,也可以通过下标对哈希表进行操作。例如利用上面的代码创建了一个哈希表,那么hmap[1] = 10;也可以通过这种方式插入元素,例如hmap[4]=20;那么现在的哈希表就是以下的内容:

hamp{ {1,10},{2,12},{3,13} ,{4,20}}

1.2.2 常见的STL 哈希表函数

常见的STL哈希表函数和其他的STL容器类似,也有迭代器,bengin、end等函数,也有insert、count等插入以及计算元素数量的函数。由于本篇文章主要阐述Lee Code的代码,因此具体内容请参考其他的文章,我也在这里给大家推荐了一篇:一文看懂哈希表并学会使用C++ STL 中的哈希表_哈希表有哪些函数-优快云博客

2.算法题详解

2.1两数之和

        暴力解法就是遍历每一个数num,然后再依次遍历寻找target-num的差值。这种方法的时间复杂度在O(n2)。

        这时哈希表就排上了用场,因为哈希表的特性,寻找元素的时间复杂度可以降至O(1)。那么如何使用哈希表呢?思路如下:

        每一次遍历一个元素num,得到target-num的差,在哈希表中寻找这个差是否存在即可。那有没有必要专门遍历一次数组,求出每个元素对应的差值存储到哈希表中,然后再重新遍历数组来找到对应的两个数呢?

        其实没必要,因为两个元素都是数组中元素,只需要顺序遍历数组,根据一个元素在哈希表中查找另一个元素即可,若不存在,则将当前元素和他的位置信息加入到哈希表中即可。可以这样理解:既然“我”找不到需要的元素,那就等其他元素来找“我”。

        因此,依照对应的思路,就有了如下的代码:

vector<int> twoSum(vector<int>& nums, int target) {
        
        int length = nums.size();
        unordered_map<int,int> hash;
        for(int i = 0; i<length;i++){
            int temp = target-nums[i];
            // 用temp存储差值,并查找temp是否在哈希表中存在。
            if(hash.find(temp) != hash.end()){
                return {i,hash[temp]};
            }
            else{
                // 若不存在,将当前元素的值和它在数组中的位置信息加入哈希表,等待后续元素来找它
                hash[nums[i]] = i;
            }
            }
        return {};
        }

2.2 字母异位词

这个题目的关键点在于摒弃只有基本类型才能做哈希表<key,value>的想法。

        如果利用暴力解法,遍历strs中的每一个字符串,利用排序算法将字符串排序,然后依次和后续的字符串排序之后的结果比较,如果想同,则将他们加入到一个vector中,最后返回这个vector<vector<string>>的结果。 同样的道理,这就面临了一个查找时的时间复杂度为O(n)的情况。 

        既然为字母异位词的字符串排序之后的结果相同,那么为什么不直接将排序之后的字符串作为哈希表的key,这样就可以在O(1)时间内完成查找。因此哈希表key的类型是string,哈希表的value类型为vector<string>,用来存储key相同的字符串。最后只需要遍历哈希表,将所有的vector<string>加入到一个vector中,然后返回即可。

 vector<vector<string>> groupAnagrams(vector<string>& strs) {
        int len = strs.size();
        unordered_map<string,vector<string>> hmap;
        for(int i = 0; i <len; i++){
            // 将排序后的结果赋给key
            string key = strs[i];
            sort(key.begin(),key.end());
            // 在对应key值的位置插入strs
            hmap[key].emplace_back(strs[i]);
        }
        // 遍历一遍hmap 返回所有结果
        vector<vector<string>> res;
        for(auto it = hmap.begin();it != hmap.end();it++){
            res.emplace_back((*it).second);
        }
        return res;
    }

2.3 最长连续序列

这个题目的解法也十分的多样化,暴力解法之外,还可以将数组排序之后,使用双指针的方法,但是这种方法需要考虑到如果数组中的元素不连续时应该怎么处理,如果出现重复元素时又应该如何处理,情况较为复杂。

显然,利用哈希表的方法就会简单很多,基本思路就是遍历一遍数组元素,看看数组中是否存在连续的元素即可。

这里的key和value的值应该如何设计呢?显然这里不需要设计value的值,只需要将数组中出现的元素设计为key即可,那么就不需要用到unordered_map这个容器,转而用到unordered_set这个容器。

基本思路为:将所有的数组元素加入到set中,遍历set,对于元素num查找是否存在num+1的元素, 如果存在则 currentnum = num 来查找后续的元素是否存在,与此同时记录最大长度max_len。 还有一点应该注意:因为set是无序的不能保证存储在里面元素的顺序,因此对于元素num查找后续的连续元素时,应该考虑到num-1的元素是否存在,如果num-1的元素存在,那么从num开始查找最长连续序列就没有意义了。

 // // 将数据去重存储,则利用集合
        unordered_set<int> my_set;
        for(const int & i : nums){
            my_set.insert(i);
        }
        // 遍历数组,在set中查看是否存在nums[i] +1的数;
        int max_len =0;
        for(const int& num : my_set){
        // 查看num-1的元素是否存在
            if(!my_set.count(num-1)){
                int currentnum = num;
                int temp_len = 1;
                while(my_set.count(currentnum+1)){
                    currentnum += 1;
                    temp_len ++;
                }
                max_len = max(max_len,temp_len);
            }
        }
        return max_len;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值