一.哈希表理论基础
1.哈希表是根据关键码的值直接进行访问的数据结构
2.哈希表通常用来快速判断一个元素是否出现在集合里。
3.哈希函数:将其他不同类型的数据格式转化成不同的数值,从而将数据映射到哈希表的索引上。
4.哈希碰撞:两种不同的数据映射到哈希表的同一索引上
解决:拉链法、线性探测法。
5.哈希三种常用数据结构:
(1)数值:适用于不太大的数值
(2)set :适用于较大的数值,不可存放相同的数据,即天然去重。
multiset :可存放相同的数据。
二者的差别原因:set 的返回值是 pair <iterator, bool>,一个返回的是迭代器,指明插入的位置,另一个返回结果:是否插入成功。所以,当第一个插入成功后,第二个相同数字便不会插入成功。 而 multiset 的返回值只是一个迭代器,故允许相同数据二次插入。
unordered_set :不可存放相同的数据,即天然去重,容器中元素的排列是无序的。
(3)map:适用于要存放的数据包括两个部分。
multimap、unordered_map。
二.有效的字母异位词
1.题目描述
给定两个字符串 s
和 t
,编写一个函数来判断 t
是否是 s
的字母异位词。
注意:若 s
和 t
中每个字符出现的次数都相同,则称 s
和 t
互为字母异位词。
示例 1:
输入: s = "anagram", t = "nagaram" 输出: true
示例 2:
输入: s = "rat", t = "car" 输出: false
2.思路分析
字母异位词:两个集合中出现的字母相同且出现的字母的次数也完全相同,只是位置不一样。
题目要求:快速判断位于 s 集合中的字母是否出现在 t 集合中,立马想到哈希法。又因为集合中字母的种类最多为26个,所以使用数组会比使用其他两个容器快得多。(使用其他两个容器,还要将每个元素通过相应的哈希函数映射为相应索引。)
既是判断 s 中的字母是否出现在 t 中,那么我们只要统计出 s 集合中,每个字母出现的次数,得到一个存放这些字母出现次数的哈希表,然后再用这个哈希表依次去减 t 集合中 每个字母出现的次数,如果得到最后的哈希表所有元素均为 0,说明两个集合的出现字母的次数相等,即他们是字母异位词,反之,若哈希表中某一单位的值不为 0 ,为正或负,则说明两个集合字母出现次数不一致或字母不一致,那他们就不是字母异味词。
代码:
class Solution {
public:
bool isAnagram(string s, string t) {
int hash[26] = {0}; //创建存放26个字母出现次数的哈希表并置空
for(int i = 0; i < s.size(); i++) { //统计s集合中出现的字母的次数
hash[s[i] - 'a']++;
}
for(int i = 0; i < t.size(); i++) { //利用hash表统计t集合中出现的字母的次数
hash[t[i] - 'a']--;
}
for(int i = 0; i < 26; i++) { //判断hash表中各元素是否为0
if(hash[i] != 0)
return false;
}
return true;
}
};
注意:
需要把字符映射到数组也就是哈希表的索引下标上,我们并不需要知道 26 个字母的ASCII码值,因为字符a到字符z的ASCII是26个连续的数值,所以字符a映射为下标0,相应的字符z映射为下标25。
再遍历 字符串s的时候,只需要将 s[i] - ‘a’ 所在的元素做+1 操作即可,并不需要记住字符a的ASCII,只要求出一个相对数值就可以了。 这样就将字符串s中字符出现的次数,统计出来了。
三.两个数组的交集
1.题目描述
给定两个数组 nums1
和 nums2
,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2] 输出:[2]
示例 2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4] 输出:[9,4] 解释:[4,9] 也是可通过的
2.思路分析
(1)使用 set 容器
数值过大,数组放不下,故使用 set 容器。题目中又要求输出结果中元素唯一,所以我们优先使用unordered_set 。
首先将 nums1 的数据映射到哈希表中,也创建出一个新的哈希表用于存放最后的数据。
将 nums1 的数据映射到哈希表后,我们就开始找 nums2 中的元素是否在 nums1 中出现,是则存放到最后的容器中,不是则继续遍历。
附代码:
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
//set 方法
unordered_set<int> result;
unordered_set<int> nums(nums1.begin(), nums1.end()); //将nums1映射到哈希表中
for(int i = 0; i < nums2.size(); i++) {
if(nums.find(nums2[i]) != nums.end()) { //找到
result.insert(nums2[i]);
}
}
return vector<int>(result.begin(), result.end());
}
};
注意:
(1)
成员方法 | 功能 |
---|---|
find(key) | 查找值为key的元素,如果找到,则返回一个指向该元素的正向迭代器;如果没找到,则返回一个与end()方法相同的迭代器 |
end() | 返回指向容器中最后一个元素之后位置的迭代器 |
所以在判断 2 中元素是否在 1 中出现过时,只要判断 nums.find()所返回的迭代器是不是与end()方法相同的迭代器即可,是就是没找到,否就是找到了。
(2)该函数要求返回的值的类型是 vector 型,我们所定义的结果类型是 unordered_set型,所以在最后返回时要做相应的变换。
(2)数组方法
因为力扣题目更新了数据要求,只要不大于1000即可,所以本题也可以使用数组方法进行求解,大致思路同上个题。
唯一不同的是,我们这里只需要让 出现在 1 中的数字在哈希表中相应位置为 1即可,代表这个数字在 1 中存在,并不需要统计他出现的次数。那么在 2 查找时直接查找 2 中元素所对应到哈希表中的位置值是否为1 ,为1则说明这个值在 1 和 2 中都出现过,直接插入到最后的结果容器中即可,(unordered_set 天然去重,所以不必担心元素重复的问题)
这里直接附代码:
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
int hash[1001] = {0};
unordered_set <int> result;
for(int i = 0; i < nums1.size(); i++) {
hash[nums1[i]] = 1;
}
for(int i = 0; i < nums2.size(); i++) {
if(hash[nums2[i]] == 1)
result.insert(nums2[i]);
}
return vector<int>(result.begin(), result.end());
}
};
四.快乐数
1.题目描述
编写一个算法来判断一个数 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
2.思路分析
从例子中我们可以看到,每做一次求和得到的 sum ,就是下一次求和的数,而在这个过程中,除非得到的sum = 1,否则会一直进行求和下去,有可能就陷入了无限循环。
那么什么情况下,就说明求和sum陷入了无限循环呢?当在做求和运算时,出现了两个相同的sum
那么本题的思路非常清晰了:将所有求出来的 sum 列到哈希表中,每次算出一个新的sum时,都要去表中查询是否有相同的值,如果有,则证明陷入循环中,立即退出;如果没有,更新要求和的值,即上一轮求出来的新sum,进行下一轮的求和。
附代码:
class Solution {
public:
int getSum(int n) { //求和函数
int sum = 0;
while(n) {
sum += (n % 10) * (n % 10);
n = n / 10;
}
return sum;
}
bool isHappy(int n) {
unordered_set<int> result;
while(1) {
int sum = getSum(n);
if(sum == 1){ //sum = 1,是快乐数,退出循环
return true;
}
else if(result.find(sum) != result.end()) { //sum!=1,判断他是否和之前的sum重复
return false;
}
else result.insert(sum); //不重复,则将新sum插入到容器中
n = sum; //更新n,进行下一轮循环
}
}
};
五.两数之和
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]
2.思路分析
此时我们要返回的是两个数的下标,但又要保存遍历过的数字,所以只能使用map容器。
利用 map 容器存放我们已经遍历过的数值及其下标,再进行下一次的遍历时,我们先从 map 容器中找,看是否有能与他相加等于 target 的值,若有,直接返回两数的下标;若没有,则将当前值和下标存放到 map 容器中,再进行下一个数值的遍历,直到找到第一对满足要求的数值或者找不到为止。
附代码:
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> result;
for(int i = 0; i < nums.size(); i++) {
int s = target - nums[i]; //与目标值相加为target的值,也是我们要在map容器中寻找的值
///auto it = result.find(s);
if(result.find(s) != result.end()) { //找到了 也可以这样写 if(it != result.end())
return {result.find(s)->second, i}; 也可写成: return {it->second, i};
}
else { //没找到,将当前值插入到map中
result.insert(pair<int, int>(nums[i], i));
}
}
return {};
}
};
注意:
(1)return 的值,要返回两个变量下标:在map中找到的符合条件的数值下标和当前数值的下标,在返回map容器中符合条件的数值下标时要注意,我们不是任意返回的,而是要返回 找到的数值的下标,找到的数值即 result.find(s),所以在返回时 是它的第二个值。也可以直接将找到的函数值定义为auto类型的it,更方便书写。
(2)在没找到时,要将当前值插入到map中,再插入时我们要注意,一次要同时插入两个值:第一个值是当前元素值 key,第二个值是当前元素的下标 value。因为要同时插入,所以要使用pair容器进行插入。
day5练习完成!
最近事真的好多!除了周六日,都没有连续完整的时间来练习coding了,不过碎片化的时间也有在好好学习。加油吧!
哈希表真的很不熟悉,所以要学习的和已经学习到的东西都非常多!