当我们想使用哈希法来解决问题的时候,我们一般会选择如下三种数据结构。
- 数组
- set (集合)
- map (映射)
这里数组就没啥可说的了,我们来看一下set。
在C++中,set 和 map 分别提供以下三种数据结构,其底层实现以及优劣如下表所示:
| 集合 | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 |
|---|---|---|---|---|---|---|
| std::set | 红黑树 | 有序 | 否 | 否 | O(log n) | O(log n) |
| std::multiset | 红黑树 | 有序 | 是 | 否 | O(logn) | O(logn) |
| std::unordered_set | 哈希表 | 无序 | 否 | 否 | O(1) | O(1) |
std::unordered_set底层实现为哈希表,std::set 和std::multiset 的底层实现是红黑树,红黑树是一种平衡二叉搜索树,所以key值是有序的,但key不可以修改,改动key值会导致整棵树的错乱,所以只能删除和增加。
| 映射 | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 |
|---|---|---|---|---|---|---|
| std::map | 红黑树 | key有序 | key不可重复 | key不可修改 | O(logn) | O(logn) |
| std::multimap | 红黑树 | key有序 | key可重复 | key不可修改 | O(log n) | O(log n) |
| std::unordered_map | 哈希表 | key无序 | key不可重复 | key不可修改 | O(1) | O(1) |
std::unordered_map 底层实现为哈希表,std::map 和std::multimap 的底层实现是红黑树。同理,std::map 和std::multimap 的key也是有序的(这个问题也经常作为面试题,考察对语言容器底层的理解)。
当我们要使用集合来解决哈希问题的时候,优先使用unordered_set,因为它的查询和增删效率是最优的,如果需要集合是有序的,那么就用set,如果要求不仅有序还要有重复数据的话,那么就用multiset。
那么再来看一下map ,在map 是一个key value 的数据结构,map中,对key是有限制,对value没有限制的,因为key的存储方式使用红黑树实现的。
哈希表经典题目
数组作为哈希表
字母异位词等元素上界确定且不大是可以使用数组替换map,这样的空间消耗更小。
字母异位词
classSolution{public:boolisAnagram(string s,string t){intrecord[26]={0};for(inti=0;i<s.size();i++){// 并不需要记住字符a的ASCII,只要求出一个相对数值就可以了record[s[i]-'a']++;}for(inti=0;i<t.size();i++){record[t[i]-'a']--;}for(inti=0;i<26;i++){if(record[i]!=0){// record数组如果有的元素不为零0,说明字符串s和t 一定是谁多了字符或者谁少了字符。returnfalse;}}// record数组所有元素都为零0,说明字符串s和t是字母异位词returntrue;}};set(集合)作为哈希表
题目没有限制数值的大小,就无法使用数组来做哈希表了。
主要因为如下两点:
- 数组的大小是有限的,受到系统栈空间(不是数据结构的栈)的限制。
- 如果数组空间够大,但哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费。
所以此时一样的做映射的话,就可以使用set了。
两个数组交集
classSolution{public:vector<int>intersection(vector<int>&nums1,vector<int>&nums2){unordered_set<int>result_set;// 存放结果,之所以用set是为了给结果集去重unordered_set<int>nums_set(nums1.begin(),nums1.end());for(intnum:nums2){// 发现nums2的元素 在nums_set里又出现过if(nums_set.find(num)!=nums_set.end()){result_set.insert(num);}}returnvector<int>(result_set.begin(),result_set.end());}};map作为哈希表
来说一说:使用数组和set来做哈希法的局限。
- 数组的大小是受限制的,而且如果元素很少,而哈希值太大会造成内存空间的浪费。
- set是一个集合,里面放的元素只能是一个key,而两数之和这道题目,不仅要判断y是否存在而且还要记录y的下标位置,因为要返回x 和 y的下标。所以set 也不能用。
map是一种<key, value>的结构,本题可以用key保存数值,用value在保存数值所在的下标。所以使用map最为合适。
两数之和
classSolution{public:vector<int>twoSum(vector<int>&nums,inttarget){std::unordered_map<int,int>map;for(inti=0;i<nums.size();i++){// 遍历当前元素,并在map中寻找是否有匹配的keyautoiter=map.find(target-nums[i]);if(iter!=map.end()){return{iter->second,i};}// 如果没找到匹配对,就把访问过的元素和下标加入到map中map.insert(pair<int,int>(nums[i],i));}return{};}};