Day6 | 454.四数相加II 383. 赎金信 15. 三数之和 18. 四数之和

58 阅读6分钟

454. 四数相加 II - 力扣(LeetCode)

给你四个整数数组 nums1nums2nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:

  • 0 <= i, j, k, l < n
  • nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0

暴力法是四层for循环,时间复杂度上为O(N4)O(N^4),显然不是最优

如果一次处理两个数组,则时间复杂度为O(2N2)O(2N^2),更优

Step1:nums1nums2的元素和作为,出现次数作为,存储为map结构

Step2: 两层for循环遍历nums3nums4,在map中寻找有没有符合题意的键值对

Step3: count+=absum[-i-j];考虑出现次数!

class Solution {
public:
    unordered_map<int,int> absum;
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        for(int i:nums1){
            for(int j:nums2){
                //两数组某元素之和:i+j作为键
                absum[i+j]++;  //如果没有i+j这个键,该语句就会执行insert
            }
        }
        int count = 0;
        for(int i:nums3){
            for(int j:nums4){
                count+=absum[-i-j];//3、4元素和为i+j,若map里有-i-j,则满足题意
                //注意,有多种出现-i-j的可能,都可计入结果
            }
        }
        return count;
    }
};

383. 赎金信 - 力扣(LeetCode)

给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。

如果可以,返回 true ;否则返回 false 。

magazine 中的每个字符只能在 ransomNote 中使用一次。

242. 有效的字母异位词 - 力扣(LeetCode)有点类似,但是字母只能多不能少。

class Solution {
public:
    unordered_map<char,int>mag_map;
    bool canConstruct(string ransomNote, string magazine) {
        for(char i : magazine){
            mag_map[i]++;   //记录mag中个字符出现次数
        }
        for(char j : ransomNote){
            if(mag_map[j]>0){//看看  有没有or够不够
                mag_map[j]--;//有 -1 代表用掉了
            }
            else return 0;//没有or已用完,不符合题意
        }
        return 1;
    }
};

15. 三数之和 - 力扣(LeetCode)(记录-去重-收缩)

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。

注意: 答案中不可以包含重复的三元组。

还在双指针!

先排序!先排序!先排序!

遍历一次数组,把遍历到的每个元素都设为头,在剩余数组里使用左右双指针

15三数之和.png 例如i走到2,左右指针初始位置分别如上图,并向中间合拢,满足条件后会返回{nums[i], nums[left], nums[right]}

注意!记录一个结果后,还应继续移动直到left>=right!!!可能还有其他组合情况满足三数之和为0

概括为:记录-去重-收缩

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> ans;
        sort(nums.begin(),nums.end());
        int size = nums.size();
        int left = 0;
        int right = 0;
        for(int i = 0 ; i<=size-3 ; i++){
            if(i>0){
                if(nums[i]==nums[i-1]) continue;
            }
            // int left = (i==0 ? 1:0);                       //这么写不对,左右指针都在i之后的区间
            // int right = (i==size-1 ? size-2:size-1);       //同上
            left = i+1;
            right = size-1;
            //************************************************************
            //这个想法很蠢,找到一个符合题意的组合就会退出循环,而且条件判断混乱
            // while(nums[i]+nums[left]+nums[right]&&right>left){
            //     if(nums[i]+nums[left]+nums[right]>0){
            //         right--;
            //     }
            //     else left++;
            // }
            // if(nums[i]+nums[left]+nums[right]==0&&left<right){
            //     ans.push_back({nums[i],nums[left],nums[right]});
            // }
            //************************************************************
            while(left<right){
                if(nums[i]+nums[left]+nums[right]==0){
                    // 找到三数之和为0的情况,记录,但不会退出while
                    ans.push_back({nums[i],nums[left],nums[right]}); 
                        
                    //**************去重******************************
                    while(left<right&&nums[left]==nums[left+1]){
                        left++;  //左去重
                    }
                    while(left<right&&nums[right]==nums[right-1]){
                        right--;  //右去重
                    }
                    //************************************************
                    
                    right--;
                    left++;
                }
                else if(nums[i]+nums[left]+nums[right]>0){
                    right--;
                }
                else left++;
            }
        }
        return ans;
    }
};

头去重

15头去重.png

nums[i]nums[i-1]的值相同时,对nums[left]nums[right]的要求是一样的,但i相比i-1向后走了一步,对应leftright的区间短了一步,因此符合条件的结果一定是i-1的子集。

因此在nums[i]=nums[i-1]前提下,i已经没有必要再处理,是上一步处理的i-1情况的子集。

代码描述如下。

for(int i = 0 ; i<=size-3 ; i++){ 
    if(i>0){ if(nums[i]==nums[i-1]) continue;   

    //后续程序...
  
}

注意不是if (nums[i] == nums[i + 1])continue; 这相当于不处理当前的i,而是处理i+1,但i+1i的子集,可能会漏解

尾去重

15尾去重.png 1. 找到符合题意的三个数后,不会立马退出while并移动i,而是继续移动leftright,寻找其他可能。

2. 如果不进行去重处理,会直接执行right--; left++;

搭配去重处理,自增自减的逻辑才完美,这里草率一点也这么处理

因为去重后,数组严格单增,同时移动leftright才可能使和不变

3. 下一次进入while循环体,if(nums[i]+nums[left]+nums[right]==0)成立,执行ans.push_back({nums[i],nums[left],nums[right]});在上图的特例中,增加了和上次一模一样的数组进入答案ans,显然不符合题意。 因此需要进行去重,代码描述如下。

 if(nums[i]+nums[left]+nums[right]==0){
    // 找到三数之和为0的情况,记录,但不会退出while
    ans.push_back({nums[i],nums[left],nums[right]}); 

    //**************去重******************************
    while(left<right&&nums[left]==nums[left+1]){
        left++;  //左去重
    }
    while(left<right&&nums[right]==nums[right-1]){
        right--;  //右去重
    }
    //************************************************

    //收缩,继续找
    right--;
    left++;
}

18. 四数之和 - 力扣(LeetCode)(记录-去重-收缩)

给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):

  • 0 <= a, b, c, d < n
  • abc 和 d 互不相同
  • nums[a] + nums[b] + nums[c] + nums[d] == target

你可以按 任意顺序 返回答案 。

和三数之和一致,多一层循环。

排序!!!

注意内层循环for(int i = k+1;i<size;i++)避免重复

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> ans;
        sort(nums.begin(), nums.end());
        int size = nums.size();
        int left = 0;
        int right = size-1;
        for(int k = 0;k<size;k++){
       
            if(k>0){   //k的头去重
                if(nums[k]==nums[k-1]) continue;
            }
            for(int i = k+1;i<size;i++){//从k+1开始,类似上三角矩阵,确保不会重复        
                if(i>k+1){   //i的头去重
                    if(nums[i]==nums[i-1]) continue;
                }
                left = i+1;
                right = size-1;
                while(left<right){
                    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{  //==target,符合题意,先记录,再去重,再收缩
                        ans.push_back({nums[k],nums[i],nums[left],nums[right]});
                        while(left<right&&nums[left]==nums[left+1]){ //左去重
                            left++;
                        }
                        while(left<right&&nums[right]==nums[right-1]){ //右去重
                            right--;
                        }
                        //收缩,继续找
                        left++;
                        right--;
                    }
                }
            }
        }
        return ans;
    }
};

补充

后面两题没写剪枝