哈希表
哈希表(又称散列表)是根据关键码的值而进行访问的数据结构。
哈希表中关键码就是数组的索引下标,然后通过下标直接访问数组中的元素,如下图:
要查询一个名字是否在一所学校里。
枚举的时间复杂度是O(n),使用哈希表则为O(1)。
哈希函数
将学生姓名映射到哈希表上的的函数就是哈希函数。
会有一个取模的操作,防止学生数量大于哈希表的大小。
哈希碰撞
学生的数量大于哈希表的大小怎么办,此时就算哈希函数计算的再均匀,也避免不了会有几位学生的名字同时映射到哈希表 同一个索引下标的位置。这种现象叫做哈希碰撞。
解决方法:拉链法和线性探测法。
拉链法:将发生冲突的元素存储于链表中。
线性探测法:保证tableSize大于dataSize。
常见的三种哈希结构
当我们使用哈希法来解决问题的时候,一般会选择如下三种数据结构:
- 数组
- set(集合)
- map(映射)
数组见上一节。
set和map分别提供三种数据结构,其底层实现以及优劣如下表:
集合 | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 |
---|---|---|---|---|---|---|
std::set | 红黑树 | 有序 | 否 | 否 | O(log n) | O(log n) |
std::multiset | 红黑树 | 有序 | 是 | 否 | O(logn) | O(logn) |
std::unordered_set | 哈希表 | 无序 | 否 | 否 | O(1) | O(1) |
映射 | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 |
---|---|---|---|---|---|---|
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) |
可以看出:
- 无前缀的都是有序的,数值也都是不可以重复的(map的底层实现以key作为红黑树的节点)。
- multi则说明数值是可以重复的,unordered则说明底层实现是哈希表,排列是没有顺序的(自然数值不可以重复)。
- 映射类型都是key value 的数据结构,以key作为节点来使用红黑树、哈希表实现的。
242.有效的字母异位词
**题意:**给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
示例 1: 输入: s = “anagram”, t = “nagaram” 输出: true
示例 2: 输入: s = “rat”, t = “car” 输出: false
说明: 你可以假设字符串只包含小写字母。
**解法:**数组。
注意养成赋初值的习惯,数组int a[26]是一定得赋初值的,vector则默认初值为0。
class Solution {
public:
bool isAnagram(string s, string t) {
if(s.size() != t.size()) return false;
vector<int> a(26, 0); // 必须得养成赋初值的习惯 int a[26]={0};
for(char c:s){
a[c - 'a']++;
}
for(char c:t){
a[c - 'a']--;
if(a[c - 'a'] < 0) return false;
}
return true;
}
};
349. 两个数组的交集
**题意:**给定两个数组 nums1
和 nums2
,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
**解法:**unordered_set法。一个us用于保存nums1,一个result_set用于记录us中与nums中重合的元素。(unordered_set在<unordered_set.h>头文件里)
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> us(nums1.begin(), nums1.end());
unordered_set<int> result;
for(int i = 0; i < nums2.size(); i++){
if(us.find(nums2[i]) != us.end()){
result.insert(nums2[i]);
}
}
return vector<int> (result.begin(), result.end());
}
};
202. 快乐数
题意:
编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」 定义为:
- 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
- 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
- 如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 false 。
**解法:**unordered_set法。一直算平方和,当出现us中出现的数字时,判断是不是等于1,等于1则为快乐数,不等于就不是。
to_string将int转为string,c - '0’可以将char转为int。
class Solution {
public:
int calsquare(int n){
string s = to_string(n);
int result = 0;
for(char c:s) result += (c - '0') * (c - '0');
return result;
}
bool isHappy(int n) {
unordered_set<int> us;
while(true){
us.insert(n);
n = calsquare(n);
if(us.find(n) != us.end()){
if(n != 1) return false;
else break;
}
}
return true;
}
};
1. 两数之和
题意:
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
**解法:unordered_map法。**因为只有唯一解,选用unordered_map。key存nums的值,value存序号。然后解的时候用当前的nums[i]去对比历史存储的unordered_map中的元素。
pair的两种表示:{a, b}、pair<int, int>(a, b)。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> umap;
for(int i = 0; i < nums.size(); i++){
auto search = umap.find(target - nums[i]);
if(search != umap.end()){
return {search->second, i};
}
umap.insert(pair<int, int>(nums[i], i));
}
return {};
}
};
454.四数相加II
题意:
给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:
- 0 <= i, j, k, l < n
- nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
**解法:**类似两数之和。unordered_map法。
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
// 需要查询的话,无序找起来才快,所以不用multiset而用unordered_map
unordered_map<int, int> umap;
for(int a : nums1){
for(int b : nums2){
umap[a + b]++;
}
}
int count = 0;
for(int c : nums3){
for(int d : nums4){
if(umap.find(0 - (c + d)) != umap.end()){
count += umap[0 - (c + d)];
}
}
}
return count;
}
};
赎金信
**题意:**给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。
如果可以,返回 true ;否则返回 false 。
magazine 中的每个字符只能在 ransomNote 中使用一次。
**解法:**哈希表,用的数组。
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
int a[26] = {0};
for(char c : magazine) a[c - 'a']++;
for(char c : ransomNote){
a[c - 'a']--;
if(a[c - 'a'] < 0) return false;
}
return true;
}
};
15. 三数之和
**题意:**给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请
你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
**解法:**双指针法。先把数组排序后,i作为for循环第一层,left从i+1开始,right从num.size()-1开始。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(), nums.end());
for(int i = 0; i < nums.size(); i++){
if(nums[i] > 0) return result;
if(i > 0 && nums[i] == nums[i - 1]) continue; // 注意是continue
int left = i + 1;
int right = nums.size() - 1;
while(left < right){
if(nums[i] + nums[left] + nums[right] > 0) right--;
else if(nums[i] + nums[left] + nums[right] < 0) left++;
else {
result.push_back(vector<int>{nums[i], nums[left], nums[right]});
while(nums[right] == nums[right - 1] && left < right) right--;
while(nums[left] == nums[left + 1] && left < right) left++;
right--;
left++;
}
}
}
return result;
}
};
18. 四数之和
**题意:**给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
注意:
答案中不可以包含重复的四元组。
示例: 给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。 满足要求的四元组集合为: [ [-1, 0, 0, 1], [-2, -1, 1, 2], [-2, 0, 0, 2] ]
**解法:**双指针法。双指针法可以将方法的时间复杂度降一个指数幂,在本题就是将O(n4)转为O(n3)。注意while去重合的时候要先判断left<right,避免nums[right - 1]或者nums[left + 1]的下标超了。
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> result;
sort(nums.begin(), nums.end());
for(int i = 0; i < nums.size(); i++){
if(i > 0 && nums[i] == nums[i - 1]) continue;
for(int j = i + 1; j < nums.size(); j++){
if(j > i + 1 && nums[j] == nums[j - 1]) continue;
int left = j + 1;
int right = nums.size() - 1;
while(left < right){
if((long) nums[i] + nums[j] + nums[left] + nums[right] > target) right--;
else if((long) nums[i] + nums[j] + nums[left] + nums[right] < target) left++;
else{
result.push_back(vector<int>{nums[i], nums[j], nums[left], nums[right]});
while(left < right && nums[right] == nums[right - 1]) right--;
while(left < right && nums[left] == nums[left + 1]) left++;
right--;
left++;
}
}
}
}
return result;
}
};
参考:
1、代码随想录/哈希表