哈希表: 哈希表是根据关键码的值直接进行访问的数据结构(数组就是一张哈希表,可以通过数组索引下标直接访问数组中的元素)。
面向的问题: 一般哈希表都是用来快速判断一个元素是否出现在集合里。
哈希表查询的时间复杂度为: O(1)
哈希函数: 索引和关键码的值之间的映射关系。
哈希冲突(碰撞):几个关键码的值同时映射到同一个索引的位置。
解决方法:
- 拉链法,发生冲突的位置存储在链表中。2. 线性探测法,冲突的位置后面在找其他空位放置其他元素。
三种哈希结构: 数组(vector/array);集合(set) ;映射(map)
集合 | 底层实现 | 是否有序 | 数值是否可以重复 | 是否能够更改数值 | 查询效率 |
---|---|---|---|---|---|
set | 红黑树 | 有序 | 否 | 否 | O(logN) |
multiset | 红黑树 | 有序 | 是 | 否 | O(logN) |
unordered_set | 哈希表 | 无序 | 否 | 否 | O(1) |
注:
- 底层实现为红黑树的数据结构,改变key的值会导致整个树的错乱,所以只能删除和增加。
- 使用集合来解决哈希问题时,优先使用unordered_set,因为它的查询和增删效率是最优的;如果需要集合是有序的,使用set;如果不仅要求有序还要求有重复数据,就用multiset。
映射 | 底层实现 | 是否有序 | 数值是否可以重复 | 是否能够更改数值 | 查询效率 |
---|---|---|---|---|---|
map | 红黑树 | 有序 | 否 | 否 | O(logN) |
multimap | 红黑树 | 有序 | 是 | 否 | O(logN) |
unordered_map | 哈希表 | 无序 | 否 | 否 | O(1) |
注: map中对key值有限制,对value的值没有限制(因为key值存储方式是使用红黑树实现的)。
总结: 当我们遇到了要快速判断一个元素是否出现过,要考虑哈希法。哈希法是牺牲了空间换时间。
242.有效的字母异位词
题目: 给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。
链接: https://leetcode.cn/problems/valid-anagram
思路:
- 题中只包含26个小写字母,可以将字母
a-z
映射到索引0-25
上。 - 只需要使用数组即可。
- 哈希函数:index = s[i] - ‘a’
- 在s里面统计key值来一遍set[index]++,在t里面统计key值来一遍set[index]–,然后看看是不是set里面的key全为0了。
class Solution {
public:
bool isAnagram(string s, string t) {
int countSet[26] = {0};
for(int i = 0; i < s.size(); ++i){
int temp = s[i] - 'a';
countSet[temp]++;
}
for(int i = 0; i < t.size(); ++i){
int temp = t[i] - 'a';
countSet[temp]--;
}
for(auto x:countSet){
if(x != 0){
return false;
}
}
return true;
}
};
时间复杂度:O(N)遍历三遍3 × N
空间复杂度:O(1)定义的是一个常数大小的数组
349.两个数组的交集
题目: 给定两个数组 nums1
和 nums2
,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
链接: https://leetcode.cn/problems/intersection-of-two-arrays/
思路:
- 先把数组nums1的值存入set,然后遍历nums2,看看set里面是否包含nums2的元素,若包含就放进ans里面。
- 一些暗示:输出结果每个元素唯一(不重复/需要去重),不考虑输出顺序(无序),使用unordered_set数据结构。
注: 如果哈希值比较少、特别分散、跨度非常大,使用数组会造成空间浪费。
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> ansTemp;
unordered_set<int> record;
for(auto num : nums1){
record.insert(num);
}
for(auto num : nums2){
if(record.count(num)){
ansTemp.insert(num);
}
}
vector<int> ans;
for(auto x:ansTemp){
ans.push_back(x);
}
return ans;
}
};
上述代码没问题,但是不够简洁(注意使用迭代器进行数组、集合的初始化),优化后的代码为:
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> ansTemp;
unordered_set<int> record(nums1.begin(), nums1.end());
for(auto num : nums2){
if(record.count(num)){
ansTemp.insert(num);
}
}
vector<int> ans(ansTemp.begin(), ansTemp.end());
return ans;
}
};
注: 使用set占用的空间比数组大,速度比数组慢,set把数值映射到key上都要做hash计算的。
**本题后面 力扣改了 题目描述 和 后台测试数据,增添了 数值范围:
- 1 <= nums1.length, nums2.length <= 1000
- 0 <= nums1[i], nums2[i] <= 1000
所以就可以 使用数组来做哈希表了, 因为数组都是 1000以内的。
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> ansTemp;
int record[1001] = {0};
for(auto num:nums1){
record[num] = 1;
}
for(auto num : nums2){
if(record[num] == 1){
ansTemp.insert(num);
}
}
vector<int> ans(ansTemp.begin(), ansTemp.end());
return ans;
}
};
总结: 将一个unordered_set替换成数组,占用内存有所减少;去重操作使用unordered_set很方便;注意使用迭代器初始化数组。
202.快乐数
题目: 编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」 定义为:
对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 false 。
思路:
- 无限循环但始终变不到 1,使用哈希法判断sum是否重复出现,可以使用
unordered_set
数据结构。 - 写一个函数进行每一次操作的计算。
class Solution {
int getSum(int n){
int sum = 0;
while(n){
int temp = n % 10;
sum += temp * temp;
n /= 10;
}
return sum;
}
public:
bool isHappy(int n) {
unordered_set<int> sumBuffer;
int sum = getSum(n);
while(sum != 1){
if(!sumBuffer.count(sum)){
sumBuffer.insert(sum);
sum = getSum(sum);
}
else{
return false;
}
}
return true;
}
};
1.两数之和
题目: 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
链接: https://leetcode.cn/problems/two-sum
思路:
- 使用哈希法-映射的数据结构,一个
key = target- nums[i]
对应一个value = (nums中的数)的下标
,无序,可以使用unordered_map
。 - 一些暗示:每种输入只会对应一个答案;数组中同一个元素在答案里不能重复出现。
- 在遍历数组的时候,只需要向map去查询是否有和目前遍历元素,如果有,就找到的匹配对,如果没有,就把目前遍历的元素比配的数值放进map中,因为map存放的就是我们访问过的元素比配的数值。
注: map<key, value>判断key值元素是否存在。 - find()返回一个迭代器:找到,返回迭代器指向要查找的元素;未找到,返回迭代器指向end。
- count()返回
0/1
:0
未找到,1
找到。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
vector<int> ans;
unordered_map<int, int> map;
for(int i = 0; i < nums.size(); ++i){
//查看一下之前是否有元素 = target - nums[i]
if(map.count(nums[i])){
ans.push_back(i);
ans.push_back(map[nums[i]]);
}
//没有 就把自己的target - nums[i]存进去
else{
map[target - nums[i]] = i;
}
}
return ans;
}
};