代码随想录算法训练营第五天|哈希表基础、242.有效的字母异位词、349. 两个数组的交集、202. 快乐数、1.两数之和
准备工作——哈希表的前前后后
- 什么是哈希表:
- 通过关键码就可以立刻找到想要的元素,这就是哈希表(如数组)
- 所谓关键码,也就是哈希函数(往往需要根据元素的特征来指定)
- 怎么用哈希表:
想要立刻在一群元素中快速、立即找到指定元素,就用哈希表(时间复杂度为O(1))
- 什么是哈希碰撞:
- 一个关键码却对应两个或多个元素,即不能通过一个关键码唯一确定某个元素,这就叫哈希碰撞。
- 处理方法:
- 发生之前尽量改进关键码的规则
- 拉链法(发生之后)——注意:表长(即tablesize)不要太长
- 线性探测法(空置的位置来存放冲突的数据)(发生之后)——注意:必须满足tablesize>datasize
4.主要的三类:
- 数组
- set
- unordered_set(优先使用)
- set(关键码有序时再用)
- multiset(关键码有序且有重复数据时再用)
- map(与set同理)
242.有效的字母异位词
题目链接:https://leetcode.cn/problems/valid-anagram/
刚学了哈希表,我就想着怎么用set、map来解答,结果卡哥用的是数组。看来能用数组还是用数组更加方便。具体代码如下:
bool isAnagram(string s, string t) {
int hash[26]={0};
for(int i=0;i<s.size();i++)
hash[s[i]-'a']++;
for(int i=0;i<t.size();i++)
hash[t[i]-'a']--;
for(int i=0;i<26;i++)
{
if(hash[i]!=0)
return false;
}
return true;
}
总体思路:
首先定义一个数组,来存储字符串里面每个字母使用的次数。
然后问题来了:数组元素是次数,但是数组下标都是0123,又不是abcd,怎么知道这是哪个字母的次数?
答:将 s[i] 的值减去 ‘a’ ,那么 s[i]-‘a’ 的结果就是0123,于是实现了abcd向0123的转化。而且这是一一对应的,a对应0,b对应1,c对应2…
hash[s[i]-'a']++
然后是最关键的逻辑:将s中的字母存入之后,再删去hash[26]里面t存在过的所有字母及个数。如果最后hash[26]里的值全部为0,则s与t是字母异位词;反之则不是。
349. 两个数组的交集
题目链接:https://leetcode.cn/problems/intersection-of-two-arrays/
解出这道题就需要理解unordered_set的性质和一些STL的基本操作,具体代码如下:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> nums1_set(nums1.begin(),nums1.end());
unordered_set<int> same;
for(int i=0;i<nums2.size();i++)
{
if(nums1_set.find(nums2[i])!=nums1_set.end())
same.insert(nums2[i]);
}
return vector<int>(same.begin(),same.end());
}
总体思路比较简单:首先将nums1存放在一个set哈希表中(set作用:去重),然后将set与nums2对比,将相同的值放入另一个哈希表same中(作用同样是去重),最后将哈希表转换成vector的形式返回就行了。
一些STL 的补充:
定义:unordered_set set;
插入:
范围插入(在初始化的时候也可以直接写)
myrecipe.insert (mypantry.begin(),mypantry.end())
;`移动插入(即直接增加一个,实现动态加减)
myrecipe.insert (make_pair<string,double>("eggs",6.0));
查找:
c++ nums1_set.find(nums2[i])
如果找不到,就返回nums1_set.end();如果找到了,就返回指向该键值对的迭代器(指针)
202. 快乐数
题目链接:https://leetcode.cn/problems/happy-number/
1.暴力法(失败)
如果n是快乐数,能够判断出来;如果n不是快乐数,就会引发无限循环,代码就运行不了了…
bool isHappy(int n) {
unordered<int> set;
int sum=0;
while(n!=0)
{
ge=n%10;
sum+=ge*ge;
n/=10;
if(n==0)
{
n=sum;
sum=0;
}
if(n==1)
return true;
}
}
2.哈希法(unordered_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.如果sum的值重复出现两次,那么就一定会陷入无限循环,那一定不是快乐数;如果sum的值没有重复出现,并不能证明最后的结果就一定是1,还需要用if(sum==1)来判断一下。
2.为什么要用哈希表?就像卡哥所说的,当你的思路到了“要判断一个数是否存在于一个集合的时候”,就要用哈希表了。
另外:这道题用到了C++d一个重要思想:封装。把求位的平方和单独打包成一个函数,下面调用即可。可以最大程度避免逻辑混乱。
1.两数之和
题目链接:https://programmercarl.com/0001.%E4%B8%A4%E6%95%B0%E4%B9%8B%E5%92%8C.html#%E6%80%9D%E8%B7%AF
这题我是二刷了,但是还是没有写对…,主要不是逻辑的问题,而是一些STL的基础问题。具体代码如下:
vector<int> twoSum(vector<int>& nums, int target) {
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 {};
map与set的不同之处在于,map是<int,int>, 是以键值对即 {键,值} 的形式保存的,不仅保存元素,还保存元素的下标;而set是,只是保存一个元素,不保存元素对应的下标。
易错点:
1.谁是键 (key) , 谁是值 (value) ?
答:map.find( ) 内括号内的东西就是键!
2.插入时也要按键值对的形式插入,即pair<int, int>(nums[i], i);
注:若 iter 为迭代器,则 key 即为 iter->first , value 即为 iter->second