代码随想录算法训练营第6天|454.四数相加II、383. 赎金信、15. 三数之和、18. 四数之和
LeetCode454
2025-01-27 17:25:34 星期一
题目描述:力扣454
文档讲解:代码随想录(programmercarl)454.四数相加II
视频讲解:《代码随想录》算法视频公开课:学透哈希表,map使用有技巧!LeetCode:454.四数相加II
代码随想录视频内容简记
梳理
关于这道题目为什么需要用到哈希法?因为按照正常的解法,需要先遍历A和B两个数组,之后将所有找到的和存放到哈希表中,之后以同样的方式遍历C和D数组,并将对应的值进行查询。
关于为什么要使用map的结构?因为按照题目要求,最后求的是符合条件的四元组的个数,所以必须要有一个value值来对所有符合条件的情况进行记录。
大致代码内容
-
遍历A和B数组。
map[a+b]++,将A和B遍历的结果存放到哈希表 -
遍历C和D数组。这里其实遍历的是之前A和B相加的结果是否在表中,所以需要查询
map[0-(c+d)]++ -
最后统计所有符合条件的value值的和
LeetCode测试
注意这个代码的时间复杂度为\(O(n)\),代码比较简单,完整四数相加Ⅱ代码如下
点击查看代码
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
unordered_map<int, int> map;
int count = 0;
for (int a : nums1) {
for (int b : nums2) {
map[a+b]++;
}
}
for (int c : nums3) {
for (int d : nums4) {
if (map.find(0-(c+d)) != map.end()) {
count = count + map.find(0-(c+d))->second;
}
}
}
return count;
}
};
LeetCode383
题目描述:力扣383
文档讲解:代码随想录(programmercarl)383. 赎金信
梳理
-
这道题和242. 有效的字母异位词很相近。一开始是没有想到的,看了一眼文档讲解,说可以定义一个数组来操作,就明白了
-
首先遍历magazine字符串,将其所有的小写字母存放到哈希表中。其实感觉数组这个哈希结构和map有相似之处,只要是连续的地址,数组中也可以存放一个“value值”(当然并不是真的value)
-
之后遍历ransomNote字符串,将对应的元素全部进行
--操作 -
最后再检查是否有元素小于0即可
LeetCode测试
点击查看代码
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
int num[26] ={0};
for (int i = 0; i < magazine.size(); i++) {
num[magazine[i] - 'a']++;
}
for (int i = 0; i < ransomNote.size(); i++) {
num[ransomNote[i] - 'a']--;
}
for (int i = 0; i < 26; i++) {
if (num[i] < 0) return false;
}
return true;
}
};
LeetCode15
题目描述:力扣15
文档讲解:代码随想录(programmercarl)15. 三数之和
视频讲解:《代码随想录》算法视频公开课:梦破碎的地方!| LeetCode:15.三数之和
代码随想录视频内容简记
这个题用哈希法会比较麻烦,用双指针的思路会比较好解
梳理
-
首先需要对数组进行排序,按照从小到大升序排列
-
遍历a的值,然后是对
a去重。针对于三个数,a,b,c。定义一个循环遍历数组得到'a' -
更新left指针和right指针
-
再内部定义一个循环遍历
b,c的值 -
然后对
b,c去重
大致代码内容
-
剪枝。
if (nums[i] > 0) continue这种情况的话直接跳过,剪掉和不剪掉并不影响最后的的结果,但是剪掉可以减少缩短运行的时间。 -
对a去重不能使用
if (nums[i] == nums[i+1])continue,这样会导致一个问题就是比如[-1,-1,2]这样的一个数组,数组内部有重复元素,这样的类型就会被直接跳过,所以需要用if (nums[i] == nums[i-1])这样的方式 -
left = i + 1,right = nums.size() - 1。对数组的指针进行更新。 -
遍历b和c,
while (right > left),这里为什么不能加上等于号呢?因为按照题目要求,三数之和的索引是不可以相等的。
if (nums[i] + nums[left] + nums[right] > 0) right--;
else if (nums[i] + nums[left] + nums[right] < 0) left++;
else result.push_back(vector<int>{nums[i], nums[left], nums[right]});
- 接下俩要对b和c进行去重。因为比如涉及到这种情况
[-1,0,0,0,0],b和c的值重复,那么接下来不能再重复添加,所以while (right > left && nums[right - 1] == nums[right]) right--;while (right > left && nums[left] == nums[left+1]) left++。
再比如这里,中间的值都是[-1,-1,-1,-1],为了避免重复添加导致result出现好多一样的[-1, -1, 2],必须要移动指针来进行去重
需要注意的是这里是一个循环,是while而不是if。同时需要注意:找到收获集之后,还需要进行left++和right--操作,保证下一次循环的b和c是不重复的。还有就是right > left这个一定要加上,要不然没有循环的出口了,会出现超时错误
LeetCode测试
代码的时间复杂度是\(O(n^2)\)。感觉确实比较复杂,细节很多
还有一点需要注意的就是在中间while的部分,不能是while (left <= right),这样会出现一种情况,就是比如原先的数组只有[-4, -1, -1, 2],正常来讲,只有[-1, -1, 2]是符合条件的,但是一旦加上等于号,就会出现有一种情况是[-4, 2, 2],就凭空多了一个2
点击查看代码
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(), nums.end());
// 遍历a
for (int i = 0; i < nums.size(); i++) {
// 特殊情况直接跳过
if (nums[i] > 0) return result;
// 对a去重
if (i > 0 && nums[i] == nums[i - 1]) continue;
int left = i + 1;
int right = nums.size() - 1;
// 遍历b,c
while (right > left) {
// 对b,c去重
if (nums[i] + nums[left] + nums[right] > 0) right--;
else if (nums[i] + nums[left] + nums[right] < 0) left++;
else {
// 找到收获集
result.push_back(vector<int>{nums[i], nums[left], nums[right]});
// 为防止收获集出现重复
while (right > left && nums[right - 1] == nums[right]) right--;
while (right > left && nums[left + 1] == nums[left]) left++;
// 找到答案时再同时收缩
left++;
right--;
}
}
}
return result;
}
};
LeetCode18
题目描述:力扣18
文档讲解:代码随想录(programmercarl)18. 四数之和
视频讲解:《代码随想录》算法视频公开课:难在去重和剪枝!| LeetCode:18. 四数之和
代码随想录视频内容简记
四数之和的思路和三数之和一摸一样,难点就在于多了一个k需要遍历,同时增加的剪枝和去重操作比较繁琐
梳理
-
首先仍然是对数组进行排序
-
一级剪枝
-
一级去重
-
二级剪枝
-
二级去重
-
和三数之和代码相同的部分
大致代码内容
- 对于四个数,首先用k对第一个数进行遍历
for (int k = 0; k < nums.size(); k++),剪枝,if (nums[k] > target && target > 0 && nums[k] > 0) continue;,这是一级剪枝,首先需要明白这里剪的是什么枝,就是当nums[k]大于target的时候,考虑的是这种情况,比如[2,3,4,5],而traget是1,那么就一定找不到最后的四元组了。
至于为什么这里不能出现负数,而要target > 0 && nums[k] > 0?是因为如果一但出现负数,那么两个数相加就有可能变小,这是我们不希望看到的,违背了我们给他排序的本意。举个例子:[-4,-1,0,0],而target是-5,如果只要nums[k] > target,那么一出现-4就会跳过,所以[-4,-1,0,0]的情况会被忽略,但是实际上,这种情况是存在的,所以负数就不适合用剪枝了。综上必须要保证为正数才能对其进行剪枝。
-
一级去重则是延续三数之和的思想,
if (k > 0 && nums[k] == nums[k-1]) continue; -
对
i进行遍历,for (int i = k + 1; i < nums.size(); i++)。首先也是剪枝,if (nums[i] + nums[k] > target && target > 0 && num[i] + nums[k] > 0) continue -
二级去重,需要进行的操作是
if (i > k + 1 && nums[i] == nums[i-1]) continue; -
剩下的部分仍然按照三数之和的思路进行编写
LeetCode测试
写代码的时候一定要先记得排序,不排序后面就乱套了
测试的时候有一个问题需要注意,正常的代码在left和right指针向中间靠拢的部分,卡在了第284个用例上,提示int型不足够计算的问题。需要在代码中添加long的转换if ((long) nums[k] + nums[i] + nums[left] + nums[right] > target) right--;
时间复杂度是\(O(n^3)\),其实就是三个嵌套循环,如果看元素被操作的次数看不出来的话,就看循环之间的关系。完整四数之和代码如下:
点击查看代码
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> result;
sort(nums.begin(), nums.end());
for (int k = 0; k < nums.size(); k++) {
if (nums[k] > target && nums[k] > 0 && target > 0) continue;
if (k > 0 && nums[k] == nums[k-1]) continue;
for (int i = k + 1; i < nums.size(); i++) {
if (nums[i] + nums[k] > target && nums[i] + nums[k] > 0 && target > 0) continue;
if (i > k + 1 && nums[i] == nums[i-1]) continue;
int left = i + 1;
int right = nums.size() - 1;
while (right > left) {
if ((long) nums[k] + nums[i] + nums[left] + nums[right] > target) right--;
else if ((long) nums[k] + nums[i] + nums[left] + nums[right] < target) left++;
else {
result.push_back(vector<int>{nums[k], nums[i], nums[left], nums[right]});
while (right > left && nums[left] == nums[left + 1]) left++;
while (right > left && nums[right] == nums[right - 1]) right--;
left++;
right--;
}
}
}
}
return result;
}
};
浙公网安备 33010602011771号