文章目录
- 242.有效的字母异位词
- 哈希法解法
- 解题思路
- 伪代码
- c++代码
- 349.两个数组的交集
- set解决
- 伪代码
- c++代码
- 数组解决
- 202.快乐数
- 思路
- 伪代码讲解
- C++代码
- 1.两数之和
- 思路
- map解题过程
- 伪代码
- C++代码
- 454.四数相加II
- 整体思路
- map解题过程
- 注意事项
- 伪代码
- C++代码
《代码随想录链接》
本文基于代码随想录,写入一些自己的想法。
242.有效的字母异位词
力扣题目链接
哈希表最擅长解决的问题就是:给你一个元素,判断在这个集合里是否出现过。只要遇到类似场景,那么我们就很可能用哈希表。
这段话可以反复咀嚼,从每个哈希法的题目中理解。
哈希法解法
一般对于哈希法的思路,我们需要用到以下三种数据结构:
- 数组
- set集合
- map映射
对于元素个数比较少,我们可以使用数组;
如果元素个数过多,我们可以使用set集合;
如果元素稀疏,那么就选用map(例如0 5 1000000);
解题思路
如果定义一个数组hash[26],这个数组来记录各个字母出现的次数。对于第一个字符串,我们将其记录进数组,也就是数组各个元素+1,对于第二个字符串,我们通过各个元素-1来记录。那么如果s
和t
每个字符出现次数相同,那么数组中每个元素都应该为0,否则,里面就是出现某个元素是非零的情况
伪代码
int hash[26];
for (i = 0; i < s.size; i++)
{hash[s[i]-'a']++; //典型的哈希操作,我们不需要知道字母a的ASCII码是多少,只需要把它对应的减下去,那0就对应a
}
for (j = o, j < t.size; j++)
{hash[t[i]-'a']--;
}
for (i = 0; i < 26;i++)if (hash[i] != 0 ) return false;
c++代码
class Solution {
public:bool isAnagram(string s, string t) {int record[26] = {0};for (int i = 0; i < s.size(); i++) {// 并不需要记住字符a的ASCII,只要求出一个相对数值就可以了record[s[i] - 'a']++;}for (int i = 0; i < t.size(); i++) {record[t[i] - 'a']--;}for (int i = 0; i < 26; i++) {if (record[i] != 0) {// record数组如果有的元素不为零0,说明字符串s和t 一定是谁多了字符或者谁少了字符。return false;}}// record数组所有元素都为零0,说明字符串s和t是字母异位词return true;}
};
349.两个数组的交集
力扣题目链接
哈希表最擅长解决的问题就是:给你一个元素,判断在这个集合里是否出现过。只要遇到类似场景,那么我们就很可能用哈希表。
set解决
如果用上面的逻辑我们就很好理解这道题了:
-
构造一个哈希表:对于nums1我们要把他进行处理,转换成哈希表;
-
给你一个元素,判断在这个集合里是否出现过:然后我们拿
nums2
的元素去判断在哈希表的集合里是否出现过。如果出现过,我们就讲该元素存入result
。
这里小小记录一下几个不同的set
set
和multi_set
底层实现都是红黑树unordered_set
. 底层实现是哈希值直接映射,可以理解为一个无限存装的数组。
上述三个具体如何选择呢?《代码随想录》
本题中我们选择unordered_set,因为它在做映射的过程中效率最高,取值操作效率也是最高的。因为另外两个的底层是树,取值的时候还有一个查找的过程
伪代码
unorder_set result; //由于题目中要求去重,不如直接设置一个set变量,他会自动去重
unorder_set num_set(nums1);
for (i = 0; i < nums2.size; i++)if(nums_set.find(nums2[i]) != nums_set.end()) //如果找到了该元素result.insert(nums2[i]);
return vector(result);
c++代码
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());}
};
数组解决
其实就是用数组来构造哈希表
class Solution {
public:vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {unordered_set<int> result_set; // 存放结果,之所以用set是为了给结果集去重int hash[1005] = {0}; // 默认数值为0for (int num : nums1) { // nums1中出现的字母在hash数组中做记录hash[num] = 1;}for (int num : nums2) { // nums2中出现话,result记录if (hash[num] == 1) {result_set.insert(num);}}return vector<int>(result_set.begin(), result_set.end());}
};
202.快乐数
力扣题目链接
题目中对快乐数的描述说会 无限循环,那么也就是说求和的过程中,sum会重复出现,这对解题很重要!。
然后再次印证上文说的思想,给你一个元素,判断在这个集合里是否出现过。只要遇到类似场景,那么我们就很可能用哈希表
所以这道题目使用哈希法,来判断这个sum是否重复出现,如果重复了就是return false, 否则一直找到sum为1为止。
判断sum是否重复出现就可以使用unordered_set。
思路
现在难点就是两个
- 发现求和过程中,无限循环的含义,因为是循环,就表示sum重复出现,所以我们需要用哈希
- 求和的过程,对取数值各个位上的单数操作要熟悉
//取各个位上的单数平方之和
int getSum(int n)
{int sum = 0;while (n){sum += (n % 10) * (n % 10);n /= 10;}return sum;
}
伪代码讲解
unordered_set<int> set;
while(1) //先主动构造一个死循环,如果找到了1我们就跳出循环,如果sum开始重复,那么立即跳出
{if sum = getSum(n); //各个位数平方求和//找到1跳出循环if (sum == 1) return true; //sum开始重复,进入了死循环。没有重复的话,我们把sum先存入setif (set.find(sum) != set.end()) return false;else set.insert(sum); //把新sum赋给n,开始下一次循环n = sum;
}
C++代码
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 falseif (set.find(sum) != set.end()) {return false;} else {set.insert(sum);}n = sum;}}
};
1.两数之和
力扣题目链接
四个重点,请看完解析之后进行回答:
- 为什么想到用哈希表
- 为什么要用map,为什么要用unordered_map
- map究竟是来做什么的,这取决于我们写代码的思路
- map中的key和value分别用来存放什么,为什么?
思路
那么本题我们为什么会想到用哈希法呢?
我们用什么样的哈希表来存这个结构呢?
- 因为我们在遍历原数组的元素时,我们要存放我们之前遍历的元素。比如对于数组[2, 7, 3, 6],我们遍历到3了,我们是不是要判断一下,这个元素和我们是否之前遍历过的元素之和能不能等于我们的
target
- 因为我们不仅要知道这个元素是多少,还要知道这个元素在数组里的下标。那么就很明显了,我们需要用map来存放这种数据结构。
- 并且我们将元素作为key,下标作为value。那么为什么不将下标作为key,元素作为value呢?那就看我们到底要查找的是什么,首先一点,我们关注该元素是否出现过,所以将要查找的值作为key,这样才能达到我们的要求。map的作用就是在最快的时间内,key是否在这个map中出现过。
综上所述,我们要用map来存放我们遍历过的元素,拿正在遍历的元素与map中的元素比较即可。后面为了简化代码,我们直接拿target - 正在遍历的元素,去看map是否有元素符合。
map解题过程
map在本题中最重要的功能就是存放我们遍历过的元素。
对于nums = [2, 7, 3, 6] target:9
当我们遍历到2,我们就想找一下元素7是否被遍历,因为2+7=9.所以我们要到map里面去查询。这就体现出了我们map的功能
所以我们把2放到map中,key = 2, value = 0;并且在map里搜索7,搜不到,继续遍历数组;
当遍历到第二个元素7,查询元素2,因为7+2=9。再去map中查询。发现map里面有!那么就找到了一个符合要求的结果集。
伪代码
unordered_map (int, int) map;
//遍历数组
for (i = 0; i < nums.size; i++)
{s = target - nums[i];iter = map.find(s);if(iter != map.end) //查找这个元素在map里出现过return (iter->value, i);//如果没有在map中找到符合要求的元素,我们把当前元素存入mapmap.insert(nums[i], i);
}
//遍历之后仍然没找到
return null;
C++代码
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中寻找是否有匹配的keyauto iter = map.find(target - nums[i]); if(iter != map.end()) {return {iter->second, i};}// 如果没找到匹配对,就把访问过的元素和下标加入到map中map.insert(pair<int, int>(nums[i], i)); }return {};}
};
454.四数相加II
力扣题目链接
其实总之一句话,就是把四数相加简化成两数相加。
整体思路
- 为什么用到哈希表。
对于数组A、B、C、D。一个简单的思路,用4个for循环进行遍历,等于0就count++,时间复杂度是O(n^4)
。那么我们是不是可以只遍历A、B两个数组,我把这两个数组取出的元素a+b放到一个集合.然后再遍历C、D两个数组,把从其中取出来的元素c+d,那这个去判断存入了a+b元素的集合里有没有我们想要的元素,如果有,就count++.
- 用什么样的哈希结构解这个题呢?
这道题中,由于有➕操作,所以元素数值可能是很大的,如果用数组下标做映射,大概率不够。所以我们只能考虑set和map。又因为本题中,我们不仅要记录a+b,还要记录a+b等于同样数值的出现次数,因为题目要求我们返回符合要求的元组个数。
综上所述,我们需要用map来解决此题
map解题过程
总结一下思路
我们在A、B数组找到了a+b作为map的key,然后其出现的次数作为value。我们要将C、D中的c+d作为查询,到map中查找符合要求的a+b。
- 注意:我们拿c+d进行查询的时候,是拿0-(c+d)这个元素有没有出现在map集合中。然后我们把对应的a+b次数都算到count里面
注意事项
为什么遍历A、B和C、D而不是遍历A,然后遍历B、C、D呢?
答:时间复杂度更低
如果对于0-(c+d)的查询,在map中找到了对应的(a+b)。此时我们的count应该加多少呢?
答:记录该a+b对应的value
伪代码
unordered_map(int, int);
for(a:A)
{for(b:B){map[a+b]++;}
}for(c:C)
{for(d:D){target = 0 - (c + d);if (map.find(target != map.end))count += map[target];}
}
return count;
C++代码
class Solution {
public:int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D) {unordered_map<int, int> umap; //key:a+b的数值,value:a+b数值出现的次数// 遍历大A和大B数组,统计两个数组元素之和,和出现的次数,放到map中for (int a : A) {for (int b : B) {umap[a + b]++;}}int count = 0; // 统计a+b+c+d = 0 出现的次数// 在遍历大C和大D数组,找到如果 0-(c+d) 在map中出现过的话,就把map中key对应的value也就是出现次数统计出来。for (int c : C) {for (int d : D) {if (umap.find(0 - (c + d)) != umap.end()) {count += umap[0 - (c + d)];}}}return count;}
};