一、哈希表基础
- 可以根据key值直接访问数据,因此查找速度快;哈希表其实就是一种数据结构。
- 本质上是一个数组,有两种实现方式:数组+链表;数组+二叉树。
- 哈希表就是通过将关键值也就是key通过一个散列函数加工处理之后得到一个值,这个值就是数据存放的位置,我们就可以根据这个值快速的找到我们想要的数据。键值是哈希值指定的哈希表地址的存储数据。
- 哈希表不存储哈希值,哈希值是键的函数,是定类数据到定序数据的映射,哈希值本身是地址数据,也就是说,你给我任何一个数据-键,我都用散列函数将其转换为一个地址数据-哈希值。
用例子说明:
通过hashCode把名字转化为数值,一般hashcode是通过特定编码方式,可以将其他数据格式转化为不同的数值,这样就把学生名字映射为哈希表上的索引数字了。
哈希碰撞--拉链法和线性探测法
拉链法:发生冲突的元素被储存在链表中,通过索引查找
线性探测法:tableSize大于dataSize ,依靠哈希表中的空位来解决碰撞问题
二、有效的字母异位词
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
题目链接:. - 力扣(LeetCode)242. 有效的字母异位词 - 力扣(LeetCode). - 力扣(LeetCode)
定义一个数组,来记录字符串s里字符出现的次数;定一个数组叫做record,大小为26 就可以了,初始化为0,因为字符a到字符z的ASCII也是26个连续的数值。(范围可控的话一般用数组)
需要把字符映射到数组也就是哈希表的索引下标上,因为字符a到字符z的ASCII是26个连续的数值,所以字符a映射为下标0,相应的字符z映射为下标25
遍历字符串s的时候,只需要将 s[i] - ‘a’ 所在的元素做+1 操作即可,并不需要记住字符a的ASCII,只要求出一个相对数值就可以了。
如何检查字符串t中是否出现了这些字符,同样在遍历字符串t的时候,对t中出现的字符映射哈希表索引上的数值再做-1的操作。
其实就是看s中的元素在t中是否对应出现
时间复杂度为O(n),空间上因为定义是的一个常量大小的辅助数组,所以空间复杂度为O(1)。
C++ 代码如下:
- s 负责在对应位置增加,t 负责在对应位置减少
- 如果哈希表的值都为 0,则二者是字母异位词
-
字母是连续的,在hash里也连续,即hash[0]就是a,减去a对应的ascii值就是把a对应到下标为0的位置
class Solution {
public:
bool isAnagram(string s, string t) {
int record[26] = {0};
for (int i = 0; i < s.size(); i++) {
// 并不需要记住字符a的ASCII,只要求出一个相对数值就可以了
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,说明字符串s和t 一定是谁多了字符或者谁少了字符。
return false;
}
}
// record数组所有元素都为零0,说明字符串s和t是字母异位词
return true;
}
};
代码中的record[s[i]-'a']++;
语句会将字符s
中第i
个位置的字符减去字符'a'
(即获取字符相对于'a'
的偏移量),然后将record
数组中对应偏移量的元素加1。
三、两个数组的交集
题目链接:349. 两个数组的交集 - 力扣(LeetCode)
文章讲解:代码随想录 (programmercarl.com)
视频讲解:学透哈希表,数组使用有技巧!Leetcode:242.有效的字母异位词_哔哩哔哩_bilibili
给定两个数组,编写一个函数来计算它们的交集。
思路:输出结果中的每个元素一定是唯一的,也就是说输出的结果的去重的, 同时可以不考虑输出结果的顺序,使用set(没有数值的限制);数值很大或者元素很分散不宜用数组,会浪费存储空间
思路:
1.遍历一个数组的值存入set中。
2.遍历第二个数组判断set中是否存在并加到结果集当中,并将set中的对应数据删掉
3.返回最终结果
std::set和std::multiset底层实现都是红黑树,std::unordered_set的底层实现是哈希表, 使用unordered_set 读写效率是最高的,并不需要对数据进行排序,而且还不要让数据重复,所以选择unordered_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());
for (int num : nums2) { //遍历nums2数组中的每个元素,使用范围for循环,将当前迭代到的元素赋值给num。
// 发现nums2的元素 在nums_set里又出现过
if (nums_set.find(num) != nums_set.end()) {
result_set.insert(num);
}
}
return vector<int>(result_set.begin(), result_set.end());
}
};
数组解答:
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> result_set; // 存放结果,之所以用set是为了给结果集去重
int hash[1005] = {0}; // 默认数值为0
for (int num : nums1) { // nums1中出现的字母在hash数组中做记录
hash[num] = 1;
}
for (int num : nums2) { // nums2中出现话,result记录
if (hash[num] == 1) {
result_set.insert(num);
}
}
return vector<int>(result_set.begin(), result_set.end());
}
};
四、 快乐数
编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。如果 可以变为 1,那么这个数就是快乐数。
如果 n 是快乐数就返回 True ;不是,则返回 False 。
代码一: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 引起的循环,是的话就是快乐数,否则不是快乐数。
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;
}
};