今日学习的文章和视频链接
454文章链接: link
454视频讲解链接: link
383文章链接: link
383视频暂无讲解
15文章链接: link
15视频讲解链接: link
18文章链接: link
18视频讲解链接: link
第454题.四数相加II
看到题目第一想法
题目描述:给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:
0 <= i, j, k, l < n
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
解题思路:四数相加不用考虑去重,所以想到使用哈希表来处理本题。
我们不仅要记录a + b在集合里出现,还要统计a + b出现过多少次,之后在可以和c + d做一个映射。所以我们不仅要统计是否出现过,还要统计出现过的次数。所以我们选择使用map,用key存是否出现过,value存出现过的次数。
看完代码随想录后的想法
解题步骤:
- 首先定义 一个unordered_map,key放a和b两数之和,value 放a和b两数之和出现的次数。
- 遍历nums1和nums2数组,统计两个数组元素之和,和出现的次数,放到map中。
- 定义int变量count,用来统计 a+b+c+d = 0 出现的次数。
- 再遍历nums3和nums4数组,找到如果 0-(c+d) 在map中出现过的话,就用count把map中key对应的value也就是出现次数统计出来。
- 最后返回统计值 count 。
实现过程中遇到的困难
最开始的时候想到了先遍历一个数组,然后再遍历另外三个数组是否在对应集合中出现过。这样再遍历后面三个数组时,时间复杂度为O( n 3 n^3 n3),而先遍历前两个数组,再遍历后两个数组的时间复杂度为 O ( n 2 ) O(n^2) O(n2)。
代码
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
unordered_map<int, int> umap; //key:a+b的数值,value:a+b数值出现的次数
// 遍历nums1和nums2数组,统计两个数组元素之和,和出现的次数,放到map中
for (int a : nums1) {
for (int b : nums2) {
umap[a + b]++;
}
}
int count = 0; // 统计a+b+c+d = 0 出现的次数
// 在遍历nums3和nums4数组,找到如果 0-(c+d) 在map中出现过的话,就把map中key对应的value也就是出现次数统计出来。
for (int c : nums3) {
for (int d : nums4) {
if (umap.find(0 - (c + d)) != umap.end()) {
count += umap[0 - (c + d)];
}
}
}
return count;
}
};
383. 赎金信
看到题目第一想法
题目描述:给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。
如果可以,返回 true ;否则返回 false 。
magazine 中的每个字符只能在 ransomNote 中使用一次。
解题思路:发现本题与之前的242.有效的字母异位词相似,只是本题是求 字符串a能否组成字符串b,而不用管字符串b 能不能组成字符串a。
看完代码随想录后的想法
判断第一个字符串ransom能不能由第二个字符串magazines里面的字符构成时需注意两点:
- 第一点“magazine 中的每个字符只能在 ransomNote 中使用一次”
- 第二点ransomNote 和 magazine 由小写英文字母组成
因为本题目只有小写字母,所以采用一个长度为26的数组来记录magazine里字母出现的次数。
再用ransomNote去验证这个数组是否包含ransomNote所需的所有字母。
使用哈希法来解决本题。
实现过程中遇到的困难
本想着使用map,但是感觉有点大材小用,看了代码随想录了解到在本题的情况下,使用map的空间消耗要比数组大一些的,因为map要维护红黑树或者哈希表,而且还要做哈希函数,是费时的!数据量大的话就能体现出来差别了。 所以数组更加简单直接有效!
代码
class Solution {
public:
bool isAnagram(string s, string t) {
int record[26]={
0};
for(int i = 0;i < s.size();i++){
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){
return false;
}
}
return true;
}
};
第15题. 三数之和
看到题目第一想法
题目描述:
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意: 答案中不可以包含重复的三元组。
解题思路:依然想着使用哈希法去解决本问题,但题目中要求我们去重,但是对去重的部分思路不是很清晰。
看完代码随想录后的想法
随想录中给了哈希法和双指针法两种解法。
哈希法
先使用两层for循环确定a + b,然后使用哈希法来确定 0-(a+b) 是否在 数组里出现过。
但要把符合条件的三元组放进vector中,然后再去重,这样是非常费时的,很容易超时。
时间复杂度可以做到O(n^2),但还是比较费时的,因为不好做剪枝操作。
双指针法
双指针法要比哈希法高效,我们看如下动画:
首先将数组排序,然后有一层for循环,i从下标0的地方开始,同时将一个下标left 定义在i+1的位置上,定义下标right 在数组结尾的位置上。
依然还是在数组中找到 abc 使得a + b +c =0,我们这里相当于 a = nums[i],b = nums[left],c = nums[right]。
移动left 和right:
- 如果nums[i] + nums[left] + nums[right] > 0 就说明 此时三数之和大了,因为数组是排序后了,所以right下标就应该向左移动,这样才能让三数之和小一些。
- 如果 nums[i] + nums[left] + nums[right] < 0 说明 此时 三数之和小了,left 就向右移动,才能让三数之和大一些。
直到left与right相遇为止。
实现过程中遇到的困难
对去重的操作还不是很熟悉
去重逻辑思考
其实主要考虑三个数的去重。 a, b ,c, 对应的就是 nums[i],nums[left],nums[right]
a的去重
a 如果重复了怎么办,a是nums里遍历的元素,那么应该直接跳过去。
但是是判断 nums[i] 与 nums[i + 1]是否相同,还是判断 nums[i] 与 nums[i-1] 是否相同?都是和 nums[i]进行比较,是比较它的前一个,还是比较他的后一个?
如果我们比较它的后一个:
if (nums[i] == nums[i + 1]) {
// 去重操作
continue;
}
那就我们就把 三元组中出现重复元素的情况直接pass掉了。 例如{-1, -1 ,2} 这组数据,当遍历到第一个-1 的时候,判断 下一个也是-1,那这组数据就pass了。
但要记住我们是不能有重复的三元组,但三元组内的元素是可以重复的!
此时这里是有两个重复的维度。
所以应该比较它的前一个:
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
这么写就是当前使用 nums[i],我们判断前一位是不是一样的元素,在看 {-1, -1 ,2} 这组数据,当遍历到 第一个 -1 的时候,只要前一位没有-1,那么 {-1, -1 ,2} 这组数据一样可以收录到 结果集里。
b与c的去重
当去重的逻辑中加入
代码
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> result_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());
}
};
第202题. 快乐数
看到题目第一想法
题目描述:
编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。如果 可以变为 1,那么这个数就是快乐数。
如果 n 是快乐数就返回 True ;不是,则返回 False 。
有如下想法:
关键:** 若无限循环,那么求和的过程中,sum会重复出现**
所以采用哈希法来判断这个sum是否重复出现,如果重复了就是return false, 否则一直找到sum为1为止。
判断sum是否重复出现就可以使用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. 两数之和
看到题目第一想法
题目描述:
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
本题解决有困难,对map不是很了解。
看完代码随想录后的想法
本题需要一个集合来存放我们遍历过的元素,然后在遍历数组的时候去询问这个集合,某元素是否遍历过,也就是 是否出现在这个集合。
本题我们不仅要知道元素有没有遍历过,还要知道这个元素对应的下标,需要使用 key value结构来存放,key来存元素,value来存下标,那么使用map正合适。
为何使用之前用过的set呢?
- 数组的大小是受限制的,而且如果元素很少,而哈希值太大会造成内存空间的浪费。
- set是一个集合,里面放的元素只能是一个key,而两数之和这道题目,不仅要判断y是否存在而且还要记录y的下标位置,因为要返回x 和 y的下标。所以set 也不能用。
而map是一种key value的存储结构,可以用key保存数值,用value在保存数值所在的下标。
在map在C++的3种类型中,选择std::unordered_map,因为这道题目中并不需要key有序,选择std::unordered_map 效率更高。
使用map需注意如下两点:
- map用来做什么
- map中key和value分别表示什么
关于第一点,map目的用来存放我们访问过的元素,因为遍历数组的时候,需要记录我们之前遍历过哪些元素和对应的下标,这样才能找到与当前元素相匹配的(也就是相加等于target)
关于第二点,这道题我们需要给出一个元素,判断这个元素是否出现过,如果出现过,返回这个元素的下标。
判断元素是否出现,这个元素就要作为key,所以数组中的元素作为key,有key对应的就是value,value用来存下标。
所以 map中的存储结构为 {key:数据元素,value:数组元素对应的下标}。
在遍历数组的时候,只需要向map去查询是否有和目前遍历元素比配的数值,如果有,就是找到的匹配对,如果没有,就把目前遍历的元素放进map中,因为map存放的就是我们访问过的元素。
实现过程如下图:
实现过程中遇到的困难
对取数值各个位上的单数操作不是很熟悉
代码
今日收获
1.对哈希表的基础理论有所了解
2.知道了set和map的应用场景
3.使用模板库还是不太熟练,后续需要加强
今日学习时长3h
该文章图片均来自Carl哥的代码随想录,在此特别感谢