哈希表理论基础
哈希表(Hash Table一般用来判断一个元素是否出现在集合中)。
解决哈希碰撞的方法:
一 开放寻址法
- 线性探测法:当发生冲突时,线性的向后面找到下一个bucket存放数值
- 二次探测法:就是在线性探测的基础上,加上平方的位置偏移量。(相对于线性探测,二次探测通过使用平方增量序列,减少了因简单线性探查可能导致的“聚集”现象,即连续冲突的元素倾向于分布在一条直线上,这有助于提高哈希表的利用率和查找效率。
由于探查序列是非线性的,它能够更好地利用哈希表中的空槽位,特别是在哈希表负载适中且冲突分布均匀的情况下。)
二 链地址法
- 在哈希值碰撞处的索引,使用链表(链表也可以使用红黑树,平衡二叉搜索树等数据结构替代)
C++中的哈希表数据结果
对于哈希表,常使用的数据结果就三种:
- 数组
- set (集合)
- map (映射)
C++中的set数据结构
名称 | 底层实现 | key是否有序 | 插入\查找 时间复杂度 |
---|---|---|---|
set | 红黑树 | 有序 | O(logn) |
multiset | 红黑树 | 有序 | O(logn) |
unordered_set | 哈希表 | 无序 | O(1) |
其中multiset是允许用重复的元素的。(map的数据结构也是这三种类型)
相关知识:红黑树
242. 有效的字母易位词
题目:给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。
示例 1:
输入: s = “anagram”, t = “nagaram”
输出: true
示例 2:
输入: s = “rat”, t = “car”
输出: false
思路:
本题使用能包含重复元素的multi set即可
代码:
class Solution {
public:
bool isAnagram(string s, string t) {
if(s.length() != t.length()) return false;
multiset<char> m1;
for(int i = 0; i < s.length(); i++) {
m1.insert(s[i]);
}
multiset<char>::iterator tmp = m1.end();
for(int j = 0; j < t.length(); j++) {
tmp = m1.find(t[j]);
if(tmp != m1.end()) {
m1.erase(tmp);
}
}
if(m1.empty()) return 1;
return 0;
}
};
349. 两个数组的交集
题目:
给定两个数组 nums1 和 nums2 ,返回 它们的
交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
思路:
使用unordered_set即可。注意将vector转换为unordered_set时,可以使用unordered_set的构造函数,直接将vector.begin()和vector.end()两个构造器传入,构造出unordered_set。反之转换,同理。
代码:
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
// 要点:熟悉unordered_set的构建函数
// 和如何把unordered_set转换为vector
unordered_set<int> resultSet;
unordered_set<int> set1(nums1.begin(), nums1.end());
for(int num: nums2) {
if(set1.find(num) != set1.end()) {
resultSet.insert(num);
}
}
vector<int> result(resultSet.begin(), resultSet.end());
return result;
}
};
202.快乐数
题目:
编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」 定义为:
对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 false 。
示例 1:
输入:n = 19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1
示例 2:
输入:n = 2
输出:false
思路:
这题要想清楚,先将快乐数的技术结构都存在unordered_set里面,只要有一次计算的值在该set里出现了,就说明该数不可能是快乐数。它会一直循环的计算下去。然后一直计算,直到遇到满足条件的1。
但是这题利用哈希表,就是为了找出在快乐数的计算结果组成的一条链表之中,有没有出现环(即相同的结果出现在历史的计算结果中,因此为了节省数据空间,使用快慢指针的方法即可。)
时间复杂度: 每次计算 n 的快乐数时,需要的复杂度为 O(logn), 计算下一回的时间复杂度为 O(lognlogn), 所以最后整个题目的时间复杂度为 O(logn)
代码:
class Solution {
public:
int bitSquareSum(int n) {
int sum = 0;
while(n > 0)
{
int bit = n % 10;
sum += bit * bit;
n = n / 10;
}
return sum;
}
bool isHappy(int n) {
int slow = n, fast = n;
do{
slow = bitSquareSum(slow);
fast = bitSquareSum(fast);
fast = bitSquareSum(fast);
}while(slow != fast);
return slow == 1;
}
};
1. 两数之和
题目:
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
输入:nums = [3,2,4], target = 6
输出:[1,2]
示例 3:
输入:nums = [3,3], target = 6
输出:[0,1]
思路:
可以使用哈希表的map结构,For循环依次将数组的元素i,判断(target - i)是否存在于map中。若存在,则返回该两个数的坐标即可,若不存在,将[key: nums[i], value: i] 存入map中。注意存入unordered_map m的代码操作,直接用 m[ nums[i] ] = i; 即可
时间复杂度:O(n)
代码:
class Solution {
public:
vector twoSum(vector& nums, int target) {
unordered_map<int, int> um;
for(int i = 0; i < nums.size(); i++) {
if(um.find(target-nums[i]) != um.end()) {
return {i, um[target-nums[i]]};
}
um[nums[i]] = i;
}
return {};
}
};