242.有效的字母异位词
题目链接:242.有效的字母异位词
难度指数:😀🙂
暴力,2层for循环,时间复杂度是O(n^2), (不介绍)
数组就是一个简单的哈希表。
eg:字符串1:
abbc字符串2:bbac
定义一个数组 hash[26] 能放下26个字母,hash[ ] 用来统计每个字母出现的频率(次数)。
用这个数组先统计第一个字符串里每个字母出现的频率;当遍历第二个字符串时,每个字母出现的频率,在 hash[ ] 里面做对应的减法,如果最后 hash[ ] 里面所有的元素都是0,说明这两个字符串就是有效字母异位词。
说一嘴:若最终结果是
hash[ ]里面有元素是-1,-3,……,说明两个字符串中该字母出现的频率不相同,即不是有效的字母异位词。
当我们想使用哈希法来解决问题的时候,我们一般会从以下三种数据结构进行考虑:
- 数组
- set (集合)
- map(映射)
Q:什么时候用数组,什么时候用set,什么时候用map?
A:针对本题,哈希值比较小(0~25),而且范围可控(26个字母)的情况下,就采用数组。
(如果哈希值很大(
hash[ ]下标很多),就用set)
将字母a到下标0做了一个映射,不需要记住字母a的ASCII码是多少
第一个字符串s :
int hash[26];
for (int i = 0; i < s.size(); i++) {
hash[s[i] - 'a']++; //将字符串s中所有的字母出现的频率都统计在hash[]里面
}
第二个字符串t :
for (int i = 0; i < t.size(); i++) {
hash[t[i] - 'a']--;
}
最后判断这2个字符串是有效字母异位词。
最后对哈希数组进行判断
AC代码: (核心代码模式)
class Solution {
public:
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;
}
};
遇到一道题,感觉想用哈希法的时候,先看看能不能用数组,(能用数组尽量用,会比较快)
一般情况下,如果哈希值比较大,那么用数组就不太合适。
如果用set的话,每次在set里面放key,还要做一次哈希运算,做一个映射,很浪费时间。
349.两个数组的交集
题目链接:349.两个数组的交集
⚠️返回的元素是有去重的。
当数值很大,例如是上亿的话,我们想做哈希映射,那么用数组就不合适了。(数组下标放不下那么大的数,同时浪费了很多存储空间。)
🦄哈希表最擅长解决:给你一个元素,判断在这个集合里是否出现过。
只要遇到这种类似的场景,你脑中的第一个想法就是这道题可能会用到哈希表。
具体是用数组、set还是map需要具体分析。
如果数值很大,用数组就不合适了,推荐用set;
如果数值不是很大,但是数值很分散(eg:下标1, 5, 100000),就3个数,用数组下标来做映射的话,要开100000这么大的数组,很浪费空间。
思路:
把 num1 里面的所有数值放到哈希表。
遍历 num2 ,查找num2里面的元素是否在哈希表里面出现过;若有出现过,放到 result 集合,最后对 result 集合去重。
set解决:
我们使用 unordered_set ,因为它做映射的时候效率是最高的;做取值操作的时候效率也是最高的。
(插一嘴:因为 set 和 multiset 底层是树,我们在取值的时候,它还有一个查找的过程。)
在初始化的时候,直接就把 num1 放进哈希表里面,
接下来就要拿 num2 进行一个查询操作。
遍历 num2 的过程:
for (int i = 0; i < num2.size(); i++) { //遍历num2的过程 for (int num : num2)
//判断在这个哈希表里面有没有出现过
if (num_set.find(num) != num_set.end()) { //找到了元素
//把它放在result集合里
result_set.insert(num);
}
}
就算你insert了100个2,最终也只能存下一个2。
因为用了 unordered_set,自动帮我们去重。
(插一嘴:使用 multiset 是可以重复的。)
AC代码: (核心代码模式)
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) {
if (nums_set.find(num) != nums_set.end()) {
result_set.insert(num);
}
}
return vector<int>(result_set.begin(), result_set.end());
}
};
力扣修改了数值,测试数据也改了:
1 <= nums1.length, nums2.length <= 10000 <= nums1[i], nums2[i] <= 1000
因此这道题是适合用数组的。
数组解决:
AC代码: (核心代码模式)
202.快乐数
题目链接:202.快乐数
AC代码: (核心代码模式)
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.两数之和
题目链接:1.两数之和
难度指数:😀😕
这道题是哈希表中比较经典的题目,用 map 来解决。
这道题如果不知道哈希表 真的不好想别的方法。
map在本题的功能:存放我们遍历过的元素。
网友:value作为数组下标,value的下标作为数组下标对应的值,这里不知道输入数组范围,不好定义数组长度,因此不适合用数组来hash
本题要用 unordered_map,它用来存和读的效率是最高的。
样例:2,7,3,6
伪代码:
unordered_map<int, int>map;
//遍历这个数组
for (int i = 0; i < nums.size(); i++) {
//要查询的值
s = target - nums[i];
if (iter != map.end()) { //如果我们想要查找的元素在map里出现过
//此时就找到了2个元素相加等于target,返回它们对应的下标
return {iter->value, i}; //其中一个下标,另一个下标i (当前遍历的元素)
}
//把遍历过的元素所对应的key和value存到map中
}
return (); //遍历结束还没找到,return一个空集合
遍历到2,我们就要去map里面查询有没有遍历过7这个值,
AC代码: (核心代码模式)
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
std::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 {};
}
};
总结:
本题代码虽然简短,不过有4个注意点:
1️⃣为什么会想到使用哈希法?
2️⃣为什么要用 map? (同时,对于C++er,为什么要使用 unordered_map?)
3️⃣ map 究竟是用来做什么的?
4️⃣ map 里面的key是用来存什么?