总的纲领:当我们需要快速查找一个元素是否在集合中,考虑用哈希法
题意:给定两个字符串,判断两个字符串中字母是否一样
思路1:暴力法 —— O(nlgn)
将两个字符串排个序,比较一下,相等就返回 true,要不然就返回 false
class Solution {
public:
bool isAnagram(string s, string t) {
sort(s.begin(), s.end());
sort(t.begin(), t.end());
if(s != t) {
return false;
}
return true;
}
};
思路2 :哈希法
可以用一个数组来做哈希,这个数组用来统计字符串中出现的字母的个数,数组下标 就是 s[i] - 'a' ,这个就是一种映射;
只用一个数组就可以了,统计第一个字符串的字母个数就 做 ++ 的操作,第二个字符串的字母个数就做 -- 的操作;
最后如果这个数组中有某个下标对应的值不是0 ,那就说明 两个字符串不相等 return false;
这个是第一次自己写的代码,当时没想过这就是哈希的方法,但是就是用了两个数组,其实没有必要,而且当时还被卡住 debug了一会;
class Solution {
public:
bool isAnagram(string s, string t) {
bool res;
vector<int> cnts(26,0);
vector<int> cntt(26,0);
for(int i = 0; i < s.size(); i++) {
cnts[s[i] - 'a']++;
}
for(int j = 0; j < t.size(); j++) {
cntt[t[j] - 'a']++;
}
/* for(int i =0;i<cnts.size();i++) {
cout<<cnts[i]<<' ';
}
cout<<endl;
for(int i =0;i<cnts.size();i++) {
cout<<cntt[i]<<' ';
}*/
for(int i = 0; i < cnts.size(); i++) {
if(cnts[i] == cntt[i]) {
res = true;
continue;
} else if(cnts[i] != cntt[i]){
res = false;
break;
}
}
return res;
}
};
相反,这个就比较看着清爽舒服
class Solution {
public:
bool isAnagram(string s, string t) {
vector<int> res(26,0);
for(int i = 0; i < s.size(); i++) {
res[s[i] - 'a']++;
}
for(int i = 0; i < t.size(); i++) {
res[t[i] - 'a']--;
}
for(int i = 0; i < res.size(); i++) {
if(res[i] != 0) {
return false;
}
}
return true;
}
};
题目:349. 两个数组的交集
题意:给两个数组,找两个数组中相同的元素
思路1:暴力法——O()
第一次自己写出来的,当时没考虑到还要去重,特地去查了一下vector去重的操作;
两层for循环,一个一个找,如果两个数组中有相等的元素,就插入,最后去重,返回答案;
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
vector<int> res;
vector<int> res1;
for(int i = 0; i < nums1.size(); i++) {
for (int j = 0; j < nums2.size(); j++) {
if (nums1[i] == nums2[j]) {
res.push_back(nums1[i]);
break;
}
}
}
//下面两行 vector 去重操作
sort(res.begin(), res.end());
res.erase(unique(res.begin(), res.end()), res.end());
return res;
}
};
思路2:哈希法——时间复杂度 O (n)
这里我们选择用 set 来哈希,因为题目没有给出数的范围,用数组来哈希映射,会浪费空间;
这里我们还要考虑去重,unordered_set的底层实现是哈希表, 使用unordered_set 读写效率是最高的,并不需要对数据进行排序,而且还不要让数据重复,所以选择unordered_set。
接下来:我们先对一个数组进行去重(以nums1为例),让nums1去重后,以他为标杆,去遍历nums2中的元素,如果找到了,那就插入到答案中,最后返回即可;
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> nums1_size (nums1.begin(), nums1.end());
unordered_set<int> res;
for (int i = 0; i < nums2.size(); i++) {
if (nums1_size.find(nums2[i]) != nums1_size.end()) {
res.insert(nums2[i]);/*find函数 ,如果找到元素,则返回指向该元素的迭代器,
否则返回指向集合末尾的迭代器,即set :: end();还有 set 中插入元素是insert;*/
}
}
return vector<int>(res.begin(),res.end());
}
};
题目:202. 快乐数
题意:
编写一个算法来判断一个正整数 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> res;
while(1) {
int sum = getsum(n);
if(sum == 1) {
return true;
}
if(res.find(sum) != res.end()) {
return false;
} else {
res.insert(sum);
}
n = sum;
}
}
};
题目:1. 两数之和
题意:给一个数组,一个target,数组中是否有两个数之和= target ,返回两个数的下标
思路:我们需要知道 值 和 下标,所以就需要 用 map 来做哈希,map 存的是 遍历过的元素
然后我们开始遍历,查询 target - nums[i] 是否在之前出现过, 如果出现过,就返回下标(一个是 当前数的小标 i,一个是 map 里的 iter-> second),如果没出现过,就把这个数插入到我们的map 中;整个数组遍历完后,没有符合条件的 那么就返回空 return {};
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> res;
for(int i = 0; i < nums.size(); i++) {
int s = target - nums[i];
auto iter = res.find(s);
if (iter != res.end()) {
return {iter->second, i};
} else {
res.insert({nums[i], i});
}
}
return {};
}
};
几点思考:
1.为什么要用哈希?
哈希:是用最快的时间来解决一个数是否在一个集合中出现过;
对于本题:我们是去查target - nums[i] 在之前是否出现过,所以我们可以用哈希;
2.为什么要用map来哈希?
本题需要知道具体的数值,最后还需要返回下标,需要使用 key value结构来存放,key来存元素,value来存下标,那么使用map正合适;
为什么key 存放元素,我们查找的是元素有没有出现过,map就是能在最快的时间内查找key,所以我们让元素值等于 key
3.三种map的结构区别?
std::unordered_map 底层是哈希表,读和存的效率是最高的为O(1);std::map 和std::multimap 的底层实现是红黑树。
4.map 在本题里的作用是什么?
map 是用来存 我们已经遍历过的元素