哈希表理论基础
什么时候想到用哈希表
判断某个元素是否存在于集合中时,就要考虑用哈希表。遍历需要O(n),而哈希表只需要O(1)。哈希法以空间换时间。
定义
哈希表是根据关键码直接进行访问的数据结构。eg. 数组就是哈希表,数组的下标就是哈希表的关键码,通过下标访问数组中的元素。
哈希表中的关键码可以是int,double,string等等。eg. 要查询一个名字是否在学校里,只需要初始化把所有名字都存在哈希表里(可以用multiset来实现),在查询时将某个名字作为索引就可以知道该名字是否在学校。
关于“哈希表”、“哈希函数”、“哈希碰撞”、“拉链法“、”线性探测法“的详细信息可参见文章讲解。
常见的三种哈希结构
一般使用 数组、set(集合)、map(映射) 来实现哈希法。
1、数组作为哈希表时,由于其大小可不是无限开辟的,【只有当题目给出固定大小的范围且不浪费大量存储空间时才可使用】。
2、在C++中,set的底层实现以及优劣如下表所示,【set是一个集合,里面放的元素只能是一个key】
- 使用set实现哈希法时,优先使用std::unordered_set,因为查询和增删效率最优;
- 若需要集合是有序的且无重复数值,则使用std::set;
- 若需要集合是有序的且有重复数值,则使用std::multiset。
集合 底层实现 是否有序 数值是否可以重复 能否更改数值 查询效率 增删效率 std::unordered_set 哈希表 无序 否 否 O(1) O(1) std::set 红黑树 有序 否 否 O(log n) O(log n) std::multiset 红黑树 有序 是 否 O(logn) O(logn) 3、在C++中,map的底层实现以及优劣如下表所示,
【map是一种key value的存储结构,可以用key保存数值,用value来保存数值key所对应的数据(比如下标)】
映射 底层实现 是否有序 数值是否可以重复 能否更改数值 查询效率 增删效率 std::unordered_map 哈希表 key无序 key不可重复 key不可修改 O(1) O(1) std::map 红黑树 key有序 key不可重复 key不可修改 O(logn) O(logn) std::multimap 红黑树 key有序 key可重复 key不可修改 O(log n) O(log n)
242 数组作为哈希表
讲解视频:学透哈希表,数组使用有技巧!Leetcode:242.有效的字母异位词_哔哩哔哩_bilibili
文章讲解:代码随想录 (programmercarl.com)
状态:不看题解可以想出,不过不知道这就是哈希法。建议记录作为“数组作为哈希表”的例题。
解题代码如下
bool isAnagram(string s, string t) {
//创建长度为26的int数组records,records[0]表示字母'a'出现的次数,records[1]表示字母'b'出现的次数,以此类推。
int records[26] = { 0 };
//遍历s,将各个字母出现的次数记录到records
for (int i = 0; i < s.size(); ++i) {
++records[s[i] - 'a'];
}
//遍历t,若某个字母出现一次,则将其从records中减去一次。
for (int i = 0; i < t.size(); ++i) {
--records[t[i] - 'a'];
}
//遍历records,若每个字母代表的次数都是0,说明s和t中每个字符出现的次数都相同;否则,则不同。
for (int i = 0; i < 26; ++i) {
if (records[i] != 0) {
return false;
}
}
return true;
}
349 集合set作为哈希表
讲解视频:学透哈希表,set使用有技巧!Leetcode:349. 两个数组的交集_哔哩哔哩_bilibili
文章讲解:代码随想录 (programmercarl.com)
状态:这道题做之前看了题解,记录作为“集合set作为哈希表”的例题。做本题时,不会使用STL的set的方法,因此属于直接抄题解。代码中的STL的使用方法值得记录,做题时可用。
这道题之前没有限制数值大小,故无法用数组来实现哈希法,且如果哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费,因此采用set来实现哈希法。现在限制了数组大小,既可以采用set,也可以采用数组。不过这里还是使用set来实现。
题意说明“输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。”,所以采用unordered_set作为哈希表。
思路
- 先将数组nums1存到名为nums_set的哈希表(unordered_set)中,
- 然后遍历数组nums2,
- 若nums2的元素在nums_set中出现过,说明是相交的元素,将其存到名为result_set的哈希表(unordered_set)中。
- 将结果哈希表result_set转为vector形式。
代码
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> result_set; //存放结果,之所以用set是为了给结果集去重
unordered_set<int> nums_set(nums1.begin(), nums1.end()); //将数组nums1存到名为nums_set的哈希表
for(int num : nums2){ //遍历数组nums2
if(nums_set.find(num) != nums_set.end()){ //若nums2的元素在nums_set中出现过
result_set.insert(num); //将其存到名为result_set的哈希表
}
}
return vector<int>(result_set.begin(), result_set.end()); //将结果哈希表result_set转为vector形式
}
202 集合set作为哈希表
讲解视频:无
文章讲解:代码随想录 (programmercarl.com)
状态:这道题做之前看了题解,记录作为“集合set作为哈希表”的例题。
思路
题目说"无限循环 但始终变不到 1"是false的情况,“无限循环”也就是这个值会重复出现,那么就可以用哈希表来判断之前是否出现过。
在while(1)循环中,
- 求数n每个位置上的数字的平方和,存到sum;
- 若sum==1,则return true;
- 若sum在哈希表set中,则return false;
- 若sum不在哈希表中,则将其插入哈希表;
- 令n=sum。
由于要求每个元素唯一且不要求有序,故哈希表采用unordered_set。
代码
class Solution {
public:
int getSum(int n) { //求数的各个位置上的数字的平方和
int sum = 0;
while (n != 0) {
sum += (n % 10)*(n % 10);
n /= 10;
}
return sum;
}
bool isHappy(int n) {
unordered_set<int> set;
while(1){
int sum = getSum(n); //求数n每个位置上的数字的平方和
if(sum == 1)return true;
if(set.find(sum) != set.end()) return false; //sum在哈希表set
else set.insert(sum); //若sum不在哈希表中,则将其插入哈希表
n = sum;
}
}
};
1 map作为哈希表
讲解视频:梦开始的地方,Leetcode:1.两数之和,学透哈希表,map使用有技巧!_哔哩哔哩_bilibili
文章讲解:代码随想录 (programmercarl.com)
状态:思路想出来了,没想到用map作为哈希表。记录作为“集合map作为哈希表”的例题。做本题时,不会使用STL的map的方法,因此属于直接抄题解。代码中的STL的使用方法值得记录,做题时可用。
思路
本题呢,我需要一个集合来存放我们遍历过的元素,然后在遍历数组的时候去询问这个集合,某元素是否遍历过,也就是 是否出现在这个集合。那么我们就应该想到使用哈希法了。因为本题,我们不仅要知道元素有没有遍历过,还有知道这个元素对应的下标,需要使用 key value结构来存放,key来存元素,value来存下标,那么使用map正合适。
这道题目中并不需要key有序,选择std::unordered_map 效率更高!
map目的用来存放我们访问过的元素,因为遍历数组的时候,需要记录我们之前遍历过哪些元素和对应的下标,这样才能找到与当前元素相匹配的(也就是相加等于target)。这道题我们需要给出一个元素,判断这个元素是否出现过,如果出现过,返回这个元素的下标。那么判断元素是否出现,这个元素就要作为key,所以数组中的元素作为key,有key对应的就是value,value用来存下标。所以 map中的存储结构为 {key:数据元素,value:数组元素对应的下标}。
在遍历数组的时候,只需要向map去查询是否有和目前遍历元素匹配的数值,如果有,就找到的匹配对,如果没有,就把目前遍历的元素放进map中,因为map存放的就是我们访问过的元素。过程如下图所示,
代码
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> map;
for(int i = 0; i < nums.size(); ++i){ //遍历数组
auto iter = map.find(target - nums[i]); //向map去查询是否有和目前遍历元素匹配的数值
if(iter != map.end()){ //如果有
return {iter->second, i}; //就找到的匹配对
}
else{ //如果没有
map.insert(pair<int, int>(nums[i], i)); //就把目前遍历的元素放进map中
}
}
return {};
}
};