哈希表理论基础
建议:大家要了解哈希表的内部实现原理,哈希函数,哈希碰撞,以及常见哈希表的区别,数组,set 和map。
什么时候想到用哈希法,当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。 这句话很重要,大家在做哈希表题目都要思考这句话。
文章讲解:代码随想录
哈希表理论,之前有一点了解,具体细节不够清晰,目前学习一下c++的哈希结构和标准库
哈希表
根据关键码的值而直接进行访问的数据结构
作用:快速判断一个元素是否出现在集合里
哈希函数
通过哈希码将关键码转换为数值
index = hashFunction(name)
hashFunction = hashCode(name) %tableSize
如果hashcode得到的数值大于哈希表的大小,则需要对数值再进行一次取模,则得到的数值会落在哈希表上
如果两个关键码同时映射到哈希表的同一个索引下标的位置,则会发生哈希碰撞
哈希碰撞
两个关键码同时索引到同一下表位置
解决方法:拉链法和线性探测法
- 拉链法:发生冲突的元素被存储在链表中,这样可以通过索引找到元素。
- 线性探测法:冲突之后向下找一个空位置存放冲突的数据,要求哈希表的大小一定要大于数据大小
常见的三种哈希结构
- 数组
- set(集合)
- map(映射)
在C++中,set 和 map 分别提供以下三种数据结构,其底层实现以及优劣如下表所示:
集合 | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 |
std::set | 红黑树 | 有序 | 否 | 否 | O(logn) | 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会导致整棵树的错乱,所以只能删除和增加。
std::unordered_map 底层实现为哈希表,std::map 和std::multimap 的底层实现是红黑树。同理,std::map 和std::multimap 的key也是有序的(这个问题也经常作为面试题,考察对语言容器底层的理解)
当我们要使用集合来解决哈希问题的时候,优先使用unordered_set,因为它的查询和增删效率是最优的,如果需要集合是有序的,那么就用set,如果要求不仅有序还要有重复数据的话,那么就用multiset。
map 是一个key value 的数据结构,map中,对key是有限制,对value没有限制的,因为key的存储方式使用红黑树实现的。
242.有效的字母异位词
建议: 这道题目,大家可以感受到 数组 用来做哈希表 给我们带来的遍历之处。
题目链接/文章讲解/视频讲解: 代码随想录
第一想法
创建两个字典存储每个字符中的字母,然后判断两个字典是否相等
def isAnagram(self, s: str, t: str) -> bool:
s_dict = {}
t_dict = {}
for st in s:
if st in s_dict:
s_dict[st] += 1
else:
s_dict[st] = 0
for st in t:
if st in t_dict:
t_dict[st] += 1
else:
t_dict[st] = 0
if t_dict == s_dict:
return True
else:
return False
代码随想录
将字符映射到哈希表的索引下标上,因为字符a到字符z的ASCII是26个连续的数值,所以字符a映射为下标0,相应的字符z映射为下标25。创建一个数组长度为26,存放每个字母出现的次数
再遍历字符串s的时候,只需要将s[i]-'a'所在的元素做+1操作即可,并不需要记住字符a的ASCII,只要求一个相对数值即可。
python代码
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
record = [0]*26
for i in s:
record[ord(i)-ord("a")] += 1
for i in t:
record[ord(i)-ord("a")] -= 1
for i in range(26):
if record[i] != 0:
return False
return True
c++代码
class Solution {
public:
bool isAnagram(string s, string t) {
int record[26] = {0};
for (int i=0; i<s.size();i++){
record[s[i]-'a']++;
}
for (int i=0; i < t.size();i++){
record[t[i]-'a']--;
}
for (int i=0; i<26; i++){
if(record[i] != 0){ //record数组的元素如果不为0,则一定有谁多了或少了字符
return false;
}
}
return true;
}
};
要注意:python代码中需要注意,字符串ASCII码的获取使用ord()函数,而c++中字符串可以直接相减
时间复杂度O(n), 空间复杂度 O(1)
349. 两个数组的交集
建议:本题就开始考虑 什么时候用set 什么时候用数组,本题其实是使用set的好题,但是后来力扣改了题目描述和 测试用例,添加了 0 <= nums1[i], nums2[i] <= 1000 条件,所以使用数组也可以了,不过建议大家忽略这个条件。 尝试去使用set。
题目链接/文章讲解/视频讲解:代码随想录
第一想法
用set存储数据
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
res = set()
for i in nums2:
if i in nums1:
res.add(i)
result = []
return list(res)
代码随想录
使用集合
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
return list(set(nums1) & set(nums2))
使用set,先用一个哈希表存储数组1中的所有元素,再将数组2的元素与哈希表的内容比较,再用集合存储结果,这个方法速度比我自己的方法要快。
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
table = {}
for num in nums1:
table[num] = table.get(num, 0) + 1
res = set()
for i in nums2:
if i in table:
res.add(i)
del table[i]
result = []
return list(res)
c++代码
使用unordered_set的效率最高
注意:c++定义unordered_set的方法 参考C++ set 函数的使用_c++set函数-CSDN博客
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> result_set;//存放结果
unordered_set<int> nums_set(nums1.begin(), nums1.end());//区间初始化
for (int num : nums2){
if (nums_set.find(num) != nums_set.end()){ //nums2的元素在nums_set中出现过
result_set.insert(num);
}
}
return vector<int>(result_set.begin(), result_set.end());
}
};
202. 快乐数
建议:这道题目也是set的应用,其实和上一题差不多,就是 套在快乐数一个壳子
题目链接/文章讲解:代码随想录
第一想法
自己没有什么想法
代码随想录
- 时间复杂度: O(logn)
- 空间复杂度: O(logn)
不断计算某个数的平方和,并将结果存入set中,如果某个数在set中已经出现过了,则说明进入了循环,返回false,如果结果为1,则范围true
python代码 注意python的取余和取模的算法divmod;
class Solution:
def getSum(self, n: int) -> int:
Sum = 0
while(n):
n, r = divmod(n,10)
Sum += r ** 2
return Sum
def isHappy(self, n: int) -> bool:
result = set()
while True:
n = self.getSum(n)
if (n == 1):
return True
if (n in result):
return False
else:
result. Add(n)
python方法2:可以将n转换为字符串,省去取余和取模操作
c++代码:
class Solution {
public:
int getSum(int n){
int sum=0;
while(n){
sum += (n%10)*(n%10);
n /= 10;
}
return sum;
}
bool isHappy(int n){
unordered_set<int> set;
while(1){
int sum = getSum(n);
if (sum == 1){
return true;
}
if (set.find(sum) != set.end()){//如果某个结果已经出现过,则说明进入了循环
return false;
}
else{
set.insert(sum);
}
n = sum;
}
}
};
1. 两数之和
建议:本题虽然是 力扣第一题,但是还是挺难的,也是 代码随想录中 数组,set之后,使用map解决哈希问题的第一题。
建议大家先看视频讲解,然后尝试自己写代码,在看文章讲解,加深印象。
题目链接/文章讲解/视频讲解:代码随想录
第一想法
使用dict,key为数值,value为下标
如果target-key在dict中,则返回target-key的下标和key的下标
否则将key,value添加进dict
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
result = {}
for i in range(len(nums)):
if (target-nums[i]) in result:
return [result[target-nums[i]], i]
else:
result[nums[i]] = i
代码随想录
- 时间复杂度: O(n)
- 空间复杂度: O(n)
c++代码 使用unordered_map
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
std::unordered_map <int,int> map;
for(int i = 0; i < nums.size(); i++) {
// 遍历当前元素,并在map中寻找是否有匹配的key
auto iter = map.find(target - nums[i]);
if(iter != map.end()) {
return {iter->second, i};
}
// 如果没找到匹配对,就把访问过的元素和下标加入到map中
map.insert(pair<int, int>(nums[i], i));
}
return {};
}
};