454.四数相加II
题目链接:454. 四数相加 II - 力扣(LeetCode)
视频链接:学透哈希表,map使用有技巧!LeetCode:454.四数相加II_哔哩哔哩_bilibili
文章链接:代码随想录
这道题我个人直观感觉是“1. 两数之和”的拓展,但四个数组搞得我不知道该构建哈希表来find什么(比如说我遍历到数组1的元素a,我不知道在下一个数组构成的hash表来find什么元素)。然后就去看参考视频了。
卡哥的视频解答了我的疑惑
既然我只会处理两个数组之和的情况,那么四个数组能不能先合并成两个数组来处理呢?
当然可以!我们可以让前两个数组相加划为一块,后两个元素相加划为一块。但也有可能会产生另一个问题。
这两块为什么要两两划分。我数组1为一块;数组2,3,4的和为另一块不行吗?
从算法时间复杂度来说,这样做不好。
因为当我们构建一块的过程中是要遍历相加的,这种方法的时间复杂度是O(),而两两划分的时间复杂度是O(
)。
进而衍生到如果出现“六数相加”、“八数相加”的题目,为了算法时间复杂度,划分是越平均越好。
在选用哈希数据结构的时候,也会陷入纠结。
结果不需要记录下标,那我是不是用set就能解决了呢?
其实并不对。
因为set会有去重的特性,所以当数组1的元素和数组2的元素相加的值跟set已有值重复的时候它是无法插入的。例如:[-1,-1],[1,1]这两数组之和出现了四个0。这四个0也算是四个组合,但set结构中没办法记录这个过程。所以我们可以采用map。其中,key为元素和,value为出现过的次数。上面那个例子就可以被记录成(0,4)键值对了。
个人代码如下:
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
//构造遍历前两个数组之和a+b,key为值,value为出现的次数
unordered_map<int,int> ab;
//数组大小n
int n = nums1.size(),ans = 0;
for(int i = 0;i<n;i++){
for(int j = 0;j<n;j++){
auto it = ab.find(nums1[i]+nums2[j]);
//如果ab里面key已有a+b,就把对应的value++
if(it != ab.end()){
ab[nums1[i]+nums2[j]]++;
}
//反之,插入一个新元素到ab和,出现次数为1
else{
ab.insert(make_pair(nums1[i]+nums2[j],1));
}
}
}
//遍历后两个数组的和c+d,看看在ab里面有没有-(c+d),如果有就ans加对应的val
for(int i = 0; i < n;i++){
for(int j = 0;j<n;j++){
auto it = ab.find(-(nums3[i]+nums4[j]));
if(it == ab.end()){
continue;
}
else{
ans += ab[-(nums3[i]+nums4[j])];
}
}
}
return ans;
}
};
383. 赎金信
这道题跟“242.有效的字母异位词”很想,比较简单。
个人代码如下:
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
bool ans;
//记录26个小写字母出现的次数
int c[26] = {0};
for(int i = 0;i<ransomNote.length();i++){
c[ransomNote[i]-'a']--;
}
for(int i = 0;i<magazine.length();i++){
c[magazine[i]-'a']++;
}
//遍历c数组,如果有元素小于0返回false,如果全部元素大于等于0就true
for(int i = 0;i<26;i++){
if(c[i]<0){
return false;
}
}
return true;
}
};
15. 三数之和
视频链接:梦破碎的地方!| LeetCode:15.三数之和_哔哩哔哩_bilibili
文章链接:代码随想录
这道题暴力方法要用三重循环,算法时间复杂度O()。在“
3 <= nums.length <= 3000”的条件下,一定会超时,所以用不了。
这道题去重操作比较难理解,看完视频之后实现的个人代码如下:
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> ans;
//对数组进行排序
sort(nums.begin(),nums.end());
//定义i,left和right指针
int i = 0,left,right;
//开始遍历i
for(i;i<nums.size()-2;i++){
//如果第一个元素大于零,就一定不存在这样的三元组
if(nums[i]>0){
return ans;
}
//如果nums[i]==nums[i-1],那么结果一定会重复,直接跳过即可。
if(i!=0&&nums[i]==nums[i-1]){
continue;
}
//i不重复就开始移动left和right
else{
left = i+1;
right = nums.size()-1;
while(left<right){
//如果nums[right] == nums[right+1],则必然会重复,故跳过
if(right!=nums.size()-1&&nums[right] == nums[right+1]){
right--;
}
else{
//可以开始考虑把答案放进去了
//如果结果大于0,就把right左移
if(nums[left]+nums[right]>-nums[i]){
right--;
}
//结果小于0,把left右移
else if(nums[left]+nums[right]<-nums[i]){
left++;
}
//结果等于0,把答案输入
else{
ans.push_back({nums[i],nums[left],nums[right]});
//因为right如果移动,nums[right]会变化,那么原来的left一定不满足条件,故left也得移动
left++;
right--;
}
}
}
}
}
return ans;
}
};
卡哥给的参考代码如下:
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(), nums.end());
// 找出a + b + c = 0
// a = nums[i], b = nums[left], c = nums[right]
for (int i = 0; i < nums.size(); i++) {
// 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
if (nums[i] > 0) {
return result;
}
// 错误去重a方法,将会漏掉-1,-1,2 这种情况
/*
if (nums[i] == nums[i + 1]) {
continue;
}
*/
// 正确去重a方法
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
int left = i + 1;
int right = nums.size() - 1;
while (right > left) {
// 去重复逻辑如果放在这里,0,0,0 的情况,可能直接导致 right<=left 了,从而漏掉了 0,0,0 这种三元组
/*
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) 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去重
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
// 找到答案时,双指针同时收缩
right--;
left++;
}
}
}
return result;
}
};
双指针思路大体可以通过视频去理解,过程如图3.1所示,图像来源:代码随想录

图3.1双指针解题过程
关键的难点在于去重部分,怎么样才能把a,b,c进行去重操作?
首先,为了去重和双指针方便,我们需要对原数组进行排序预处理,这个预处理方法我估计在以后很多题目都会用上。
然后解释一下参考代码里面的注释
1.为什么a的去重可以直接通过判断之后右移指针实现,这样不会漏解吗?
不会漏解,可以看到图3.2。

图3.2
如果不去重,我们先判断i指向nums[0],找出集合S1里面和为-2的所有组合C1。然后i挪到nums[1]时,找出集合S2里面和为-2的所有组合C2。
我们易得:S1包含S2,所以C1一定包含C2!所以跳过nums[1]不会漏解。
2.为什么说a去重中判断nums[i] == nums[i + 1]是错误的方法?
因为我们是对a去重,如果这样判断就会把b==a的情况也忽略过去了,这不符合题干要求。我们要做的是不能有重复的三元组,但三元组内的元素是可以重复的!
3.如何对b,c去重
这里面有两种实现方法
- 先选中一个(a,b,c)三元组之后再对b,c去重(参考代码的方法)
- 选中三元组之前对b去重,c就不用去重了(我实现的代码)
我个人觉得参考代码更好理解一些。
该解法算法时间复杂度为O()
18. 四数之和
文章链接:代码随想录
这道题目跟“三数之和”类似,多了个指针j,剪枝的思路也类似,所以就直接把代码写出来了。但有一个小细节要注意:题干里面说元素的范围是“ <= nums[i] <= ”,又因为nums元素是int,如果直接四个元素相加很容易超出int表示范围,所以我们在相加的过程中得把其中一个元素强制类型转换成long,这样的话才不会溢出。
个人代码如下:
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> ans;
int i,j,left,right;
//先对nums进行排序处理
sort(nums.begin(),nums.end());
//如果nums长度小于4或者前四个元素之和都大于target直接返回空数组
if((nums.size()<4)){
return ans;
}
else{
//从第一个指针i开始处理
for(i = 0;i<nums.size()-3;i++){
//如果i前面有重复值就跳过
if(i>0&&nums[i]==nums[i-1]){
continue;
}
//如果没有重复的话就开始处理第二个指针j,跟i的处理方式类似
else{
for(j = i+1;j<nums.size()-2;j++){
if(j>i+1&&nums[j]==nums[j-1]){
continue;
}
//开始处理left和right
else{
left = j+1;
right = nums.size()-1;
while(left<right){
//可能溢出,转成long
if(long(nums[i])+nums[j]>target-long(nums[left])-nums[right]){
right--;
}
else if(long(nums[i]+nums[j])<target-nums[left]-nums[right]){
left++;
}
else{
ans.push_back({nums[i],nums[j],nums[left],nums[right]});
//对答案进行去重
while(right>left&&nums[right] == nums[right-1]){
right--;
}
while(right>left&&nums[left] == nums[left+1]){
left++;
}
right--;
left++;
}
}
}
}
}
}
}
return ans;
}
};
算法时间复杂度是。我总结一下双指针法:
双指针法其实是暴力法的一种剪枝,将时间复杂度降一个数量级。例如:O(n^2)的解法优化为 O(n)的解法。
等之后刷完字符串再尝试对双指针法进行总结。
1323

被折叠的 条评论
为什么被折叠?



