Day06_哈希表
1. 哈希表理论基础
哈希表就是了键到值的一一映射
哈希碰撞: 由于空间是有限的,生成键值的时候会做取模等运算,可能会遇到多个值对应一个键的情况
解决方法:
- 拉链法:在同一个键值映射到的区域引出一个链表,每多一个值,就多连接一个节点
- 线性探测法:映射表的空间要比数据占用的空间大。出现冲突时,要存储的值向后寻找到第一个空位再存储。
C++中可用于hash表的三种数据结构:
- 数组
- set
- map
set(集合)
集合 | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 |
---|---|---|---|---|---|---|
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值会导致整棵树的错乱,所以只能删除和增加。
map(映射)
映射 | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 |
---|---|---|---|---|---|---|
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) |
当我们要使用集合来解决哈希问题的时候,优先使用unordered_set,因为它的查询和增删效率是最优的,如果需要集合是有序的,那么就用set,如果要求不仅有序还要有重复数据的话,那么就用multiset。
那么再来看一下map ,在map 是一个key value 的数据结构,map中,对key是有限制,对value没有限制的,因为key的存储方式使用红黑树实现的。
其他语言例如:java里的HashMap ,TreeMap 都是一样的原理。可以灵活贯通。
2. 有效的字母异位词
242. 有效的字母异位词
思路:
将26个小写字母映射到0~25的数字上,统计s中每个字母出现的次数。然后遍历t,每遇到一个相同的字母就消耗一个,如果出现不等于的字母,则该字母在两字符串中出现的次数不同。
class Solution {
public:
bool isAnagram(string s, string t) {
int my_map[26] = {0};
int s_len = s.length();
int t_len = t.length();
if (s_len != t_len) return false; // 如果两个字符串不一样长
for (int i = 0; i < s_len; ++i) { // 统计s中每个字母出现的次数
my_map[s[i] - 'a']++;
}
for (int i = 0; i < t_len; ++i) {
my_map[t[i] - 'a']--;
if (my_map[t[i] - 'a'] < 0) return false; // 如果出现t中的字母多于s中的字母,直接返回false
}
for (int i = 0; i < 26; ++i) { // 检查每个字母出现的次数是否相等
if (my_map[i] != 0) return false;
}
return true;
}
};
3. 两个数组的交集
349. 两个数组的交集
思路:
数据量只有1000,实用数组统计做hash映射即可。但是为了练练C++的set,就全用set了。将num1的数据全部插入set,然后在set中查询num2中的元素是否存在于set中,如果存在则加入result_set中(目的是去重)。
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> result_set; // 结果集合
unordered_set<int> num_set(nums1.begin(), nums1.end());
int num2_size = nums2.size();
for (int i = 0; i < num2_size; ++i) { // 查到的元素加入交集集合
if (num_set.find(nums2[i]) != num_set.end()) {
result_set.insert(nums2[i]);
}
}
vector<int> ans(result_set.begin(), result_set.end()); // 将数据放入vector返回
return ans;
}
};
4. 快乐数
202. 快乐数
思路:
如果在运算过程中的平方和在之前的计算中出现过,说明该数字运算出现了循环,否则一直运算到平方和为1,返回是快乐数。
class Solution {
public:
bool isHappy(int n) {
unordered_set<int> my_map;
int new_n = 0;
while (n != 1) {
/* 获取n的每位的平方和 */
while (n) {
new_n += pow((n%10), 2);
n /= 10;
}
n = new_n;
new_n = 0;
if (my_map.find(n) != my_map.end()) return false; // 如果出现重复的平方和,则出现循环
else my_map.insert(n); // 否则加入新出现的平方和
}
return true;
}
};
4. 两数之和
1. 两数之和
思路:
本来想先把所有的元素插入到set中,然后查找
target-nums[i]
,然后发现解决不了存在target的一半的情况。于是改用下标和元素值得映射:
首先,把当前数字作为一个元素,查找与他加和等于target的元素在不在数组中
不在,则将当前数字加入哈希映射表
在则返回当前下标和找到的下标
这种查找方法,相当于每次利用后面的数,查找前面有没有能和其加和成为target的数,没有则将当前数放入查找的映射表。如此能做到不重不漏。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
int nums_size = nums.size();
unordered_map<int, int> my_map;
for (int i = 0; i < nums_size; ++i) {
auto iter = my_map.find(target - nums[i]);
if (iter != my_map.end()) {
return {iter->second, i};
}
my_map.insert({nums[i], i});
}
return {};
}
};