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;