哈希表基础
文档讲解:哈希表理论基础
应用场景:
快速判断一个元素是否出现集合里,考虑哈希法。★★★
例如:做面试题目的时候遇到 需要判断一个元素是否出现过的场景 应该第一时间想到哈希法!
哈希法也是牺牲了空间换取了时间,因为要使用额外的空间(如数组,set或者是map)来存放数据,才能实现快速的查找。
哈希函数
哈希函数是一种将输入数据映射到固定大小的输出值的函数。它通常用于数据的索引、加密和验证等领域。哈希函数的特点是输入数据的任意微小变化都会导致输出值的巨大变化,且无法从输出值反推出输入数据。
哈希函数的主要特点包括:
- 一致性:相同的输入始终产生相同的输出。
- 高效性:计算速度快,对于给定的输入,能够在合理的时间内计算出哈希值。
- 均匀性:输出值的分布应尽可能均匀,避免冲突。
- 不可逆性:无法从哈希值反推出原始输入数据。
哈希碰撞
又名 冲突 ,即两个数值通过哈希函数映射到了一个索引小标的存储位置.
#常用解决方法
拉链法和线性探测法。
#拉链法
选择适当的哈希表的大小,这样既不会因为数组空值而浪费大量内存,也不会因为链表太长而在查找上浪费太多时间。
#线性探测法
需要 哈希表长>要存储数据的个数 , 因为是依靠哈希表中的空位来解决碰撞问题.
常见的三种哈希结构
当我们想使用哈希法来解决问题的时候,我们一般会选择如下三种数据结构。
- 数组---------------------------------------涉及的元素少时用
- set (集合)-----------------------------涉及的元素多时用
- map(映射) -------------------------------涉及的元素有key值对应的有value时用(即,快速查找某个 键对应的值)
在C++中,set 和 map 分别提供以下三种数据结构,其底层实现以及优劣如下表所示:
集合 | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 |
---|---|---|---|---|---|---|
std::set | 红黑树 | 有序 | 否 | 否 | O(log n) | O(log n) |
std::multiset | 红黑树 | 有序 | 是 | 否 | O(logn) | O(logn) |
std::unordered_set | 哈希表 | 无序 | 否 | 否 | O(1) | O(1) |
set去重操作,杠杠滴! unordered_set具体应用见349.两个数组的集合
映射 | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 |
---|---|---|---|---|---|---|
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) |
上面这两张表自己看的似懂非懂的,后续需加深理解.
242.有效的字母异位词
文档讲解/视频讲解:有效的字母异位词
初始做题状态:不会
本题背景:感受 数组 用来做哈希表 给我们带来的遍历之处
学完后想法:用了2个for()循环加加减减的,是真没想到.最后还用了一个for()循环判断数组中的元素是否为0.这道题对于哈希表的应用比我想的要简单,但如此思路确实也让我没有想到.多多练习吧!
思路关键点:1.设一个表长为26的哈希表,因为要放26个字母.
2.用此哈希表统计2个字符串中字母出现的个数.对第一个字符串出现的字母个数++,对第二个字符串出现的相同字母个数 - -,当结果等于0时,即为有效的字母异位词,反之(当等于-1/-2或+1/+2之类的),即为两个字符串出现的字母不匹配.
遇到的困难:1.record[s[i]-'a']++; //why如此? 因为这样并不需要记住字符a的ASCII,字母会自动对应相应的下标,如a->0,b->1...
上述代码我原先一直想不明白,现在是懂了
2.本题用到3个for()循环,前两个判断字母出现的个数,最后一个for()还嵌套了一个if()语句判断数组元素是否为0,自己写不容易写对呀!
今日收获+学习时长:如何用一个哈希表快速判断一个元素是否出现在集合里的思路是学会了,开心.学习时长,不详
估计有网友会感到很奇怪,我为什么总写着自己学习时长不详,其实多半原因是 学的太久才弄懂,不好意思往上放.
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']++; //why如此? 因为这样并不需要记住字符a的ASCII,字母会自动对应相应的下标,如a->0,b->1...
}
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,说明字符串s和t 一定是谁多了字符或者谁少了字符。
return false;
}
}
return true;// record数组所有元素都为零0,说明字符串s和t是字母异位词
}
};
349. 两个数组的交集
文档讲解/视频讲解:两个数组的交集
初始做题状态:不会
本题背景:本题就开始考虑 什么时候用set 什么时候用数组,本题其实是使用set的好题,但是后来力扣改了题目描述和 测试用例,添加了 0 <= nums1[i], nums2[i] <= 1000 条件,所以使用数组也可以了,不过建议大家忽略这个条件。 尝试去使用set。
学完后想法:对set和数组的不同应用懂了,但是该题代码的书写我看的不太懂,需要补基础
思路关键点:1.用第一个numbers1转化为一个哈希表,把numbers里的元素全放进哈希表里.
2.遍历第二个numbers2里的元素,去对比哈希表里的元素,看是否有相同的
3.有相同的元素,就把该元素放进一个新建的result集合里,注意元素要 去重
遇到的困难:1.选择用unordered_set ,因为unordered_set读写/取值效率是最高的,并不需要对数据进行排序,而且还不要让数据重复(直接完成 去重).
今日收获+学习时长:用set的场景:1.元素个数就很多,用数组不划算;2.元素个数就几个,但其中个别元素值特别大,用数组也装不下,so 用set. 学习时长,不详
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> result_set;//存放结果,之所以用set是为了给结果集去重
unordered_set<int> nums_set(nums1.begin(),nums1.end());//把nums1放进哈希表里
for(int num : nums2){//遍历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的应用,其实和上一题差不多,就是 套在快乐数一个壳子
学完后想法:
思路关键点:
遇到的困难:
今日收获+学习时长:
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;
}
// 如果这个sum曾经出现过,说明已经陷入了无限循环了,立刻return false
if (set.find(sum) != set.end()) {
return false;
} else {
set.insert(sum);
}
n = sum;
}
}
};
1. 两数之和 (未完)
文档讲解/视频讲解:两数之和
初始做题状态:不会
本题背景:本题虽然是 力扣第一题,但是还是挺难的,也是 代码随想录中 数组,set之后,使用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 {};
}
};