day6
2023.12.4
代码随想录
今天开始新的结构—哈希表;也就是散列表,哈希表的理论知识在考研是滚瓜烂熟,各种哈希函数、冲突解决方法、平均查找长度等等。不过还是想吐槽一下,当时专业课考试的哈希表题给我做懵了,至今记忆犹新,原题目:给了两个哈希函数,然后表中已经插入了两个值,让把剩下值插入。。。当时看了半天,不会,不知道怎么插的,两个哈希函数的作用也不知道。。。第一小问都不会。。更别提第二问求平均查找长度了。现在也不清楚那道题到底怎么做。。。ok,言归正传,对于哈希表的相关用法,好像从没写过相关代码,仅接触理论知识,希望借此机会能提升一下相关代码水准
1. 242有效的字母异位词
这道题开始有些束手无策,很呆瓜的办法,遍历然后看每个字母出现次数,对比即可。难不成26个case?emmm,显然不是正解,也是看了代码随想录的文字讲解才懂了哈希表的做法,一种映射机制。个人看来,本题方法有两个关键点
1.映射机制,26个英文字母,对应0-25,并且字母-'a’就是对应的数字,感觉很巧妙,学到了
2.开始以为遍历两个字符串后,对比两个哈希表数组是否相等即可,但空间开销大一点,正解是在第一个数组记录时++,在遍历第二个字符串时–,如果满足要求,最终数组应该都是0,很巧妙,学会了。
class Solution {
public:
bool isAnagram(string s, string t) {
int reword[26] = {0};
for(int i=0;i<s.size();i++){
reword[s[i]-'a']++;
}
for(int i =0;i<t.size();i++){
reword[t[i]-'a']--;
}
for(int i=0;i<26;i++){
if(reword[i]!=0)
return false;
}
return true;
}
};
2. 349两个数组的交集
这道题有了第一题的思想,还是挺简单了,不过空间开销能大一点,两个数组表示哈希表(题目给了数值范围),数组下标对应给的两个数组的值,也是一种映射,最后比较两个数组,如果同时都不为0,则将索引存入结果数组中,这是我得写法,很普通,时间开销也不大,但就是空间开销大了点。
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
int res1[1000] = {0};
int res2[1000] = {0};
vector<int> result;
for(int i=0;i<nums1.size();i++){
res1[nums1[i]]++;
}
for(int i=0;i<nums2.size();i++){
res2[nums2[i]]++;
}
for(int i = 0;i<1000;i++){
if(res1[i]!=0 && res2[i]!=0)
result.push_back(i);
}
return result;
}
};
不过代码随想录中给了一种新的写法,很方便,相比之下之前代码就很冗余了。以下是代码随想录中的解释:
而且如果哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费。
此时就要使用另一种结构体了,set ,关于set,C++ 给提供了如下三种可用的数据结构:
- std::set
- std::multiset
- std::unordered_set
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的元素 在nums_set里又出现过
if (nums_set.find(num) != nums_set.end()) {
result_set.insert(num);
}
}
return vector<int>(result_set.begin(), result_set.end());
}
};
3. 202快乐数
这道题还是挺新颖的,刚看到还是有些懵,首先求和就有点困难,其次就是本题的关键点,怎么不是快乐数?也就是无限循环,但怎么会无限循环呢,肯定是有个数重复出现了,因此一直重复该过程,导致无限循环,所以大体思路就是,将每次sum存放在一个结构中,然后判断和是否为1,是则true,不是则继续循环,并且判断新sum与之前所有sum是否存在重复,一旦有重复,则false,不是快乐数
判断一个数是否在一个集合中,哈希表最快捷!因此本次最优解是使用哈希表。
class Solution {
public:
int getNum(int n){
int num=0;
while(n){
num += (n%10)*(n%10);
n = n/10;
}
return num;
}
bool isHappy(int n) {
unordered_set<int> result;
while(1){
int num = getNum(n);
if(num==1)
return true;
if(result.find(num)!=result.end()){
return false;
}
else{
result.insert(num);
}
n = num;
}
}
};
4. 1两数之和
这道题看似简单,实则还是有难度的感觉,简单的暴力遍历就不说了,肯定不是我们想要的正解,我们想要一个循环解决,每次循环体代表一个数,想找到另一个数以匹配target,是不是也是在一个集合中找到某个值?这不就是哈希表的用处么,不过不同的是,暴力for循环是在内层for中遍历寻找的,而我们要的是不用for,我们定义一个map,key表示值,value表示索引,遍历中如果当前值不满足要求,就放入map中,然后每次for循环在map中找需要的另一个匹配的值。有点在之前值里找答案的感觉,而两层for是在之后未知数据中找值,这是我个人的理解。如果遍历完后,找不到值,返回空,找到了,将对应循环游标i和map的key返回即可
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
std::unordered_map<int, int> temp;
for(int i=0;i<nums.size();i++){
auto n = temp.find(target-nums[i]); //在以往存储的map中找另一个值
if(n!=temp.end()){ //这是找不到的情况,也就是遍历完后最终返回最后一个值
return {n->second, i};
}
temp.insert(pair<int,int>(nums[i],i));
}
return {};
}
};