代码随想录算法训练营第七天|454.四数相加II、383. 赎金信、15. 三数之和、18. 四数之和

454.四数相加II

力扣454

这道题比“四数之和”简单的一个地方在于,从四元组中找到的组合不需要去重,找到多少个算多少个。

如果四个元组分别为ABCD,整体思路是,把AB遍历完,将a+b的值放到一个集合里,然后再遍历CD时,去判断集合中有没有我们需要的-(c+d)使得四数之和为0。因此确定本题采用哈希法。

本题数组下标来做映射的话,占用存储空间太大,所以排除数组。又因为需要统计符合条件的组合的个数,所以,此处采用map中的unordered_map

class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        unordered_map<int,int> mymap;
        int count = 0;
        for(int a:nums1){
            for(int b:nums2){
                mymap[a+b]++;
            }
        }
        for(int c:nums3){
            for(int d:nums4){
                int t = -(c+d);
                if(mymap.find(t)!=mymap.end()){
                    count+=mymap[t];
                }
            }
        }
        return count;
    }
};

有一个要注意的是,在找到符合条件的abcd后不是用count++计数,而是count+=mymap[-(c+d)],因为找到一个-(c+d)对应的a+b的组合有几个就应该计几个。

383. 赎金信

力扣383

本题判断 ransomNote 能不能由 magazine 里面的字符构成。magazine 中的每个字符只能在 ransomNote 中使用一次。这道题其实和有效的字母异位词非常相似,只需要稍稍改动一下:

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        int hash[26];
        for(int i=0;i<26;i++){
            hash[i]=0;
        }
        for(int i=0;i<magazine.size();i++){
            hash[magazine[i]-'a']++;
        }
        for(int i=0;i<ransomNote.size();i++){
            hash[ransomNote[i]-'a']--;
        }
        for(int i=0;i<26;i++){
            if(hash[i]<0) return false;
        }
        return true;
    }
};

将最后一个for循环的判断if条件改为hash【i】<0即可,即,判断ransomNote使用magazine中的字母后有没有多用超额。

简化代码:

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        int hash[26]={};
        for(int i=0;i<magazine.size();i++){
            hash[magazine[i]-'a']++;
        }
        for(int i=0;i<ransomNote.size();i++){
            hash[ransomNote[i]-'a']--;
            if(hash[ransomNote[i]-'a']<0) return false;
        }
        return true;
    }
};

15. 三数之和

力扣15

如果用计算四数相加的方法同样计算三数之和的话,就无法考虑到本题要求的去重操作。

使用哈希法的话,去重的细节非常繁琐。

此处采用代码随想录 中提到多次的双指针法: 

-1-1011
ileftright
abc

要求a+b+c=0 。

大致思路

先让数组从小到大排序。for循环遍历i,指代三数中最小的那个数a。

另外两个数,则用left和right指代,left = i+1,right = 最后一个数。

通过这样的指针分配方式,我们可以知道:

当这三数之和大于0时,需要缩小,right左移;当小于0时,需要增大,left右移。

当正好等于0时,就可以把三个数放入结果集。由nums[i]nums[left]nums[right]组成的一维vector添加到result这个二维vector的末尾。(易错点,误写成result.push_back(vector<vector<int>>{nums[i],nums[left],nums[right]});  )

去重细节

1. 剪枝

如果a作为最小的数,已经大于0了,那么无论如何三数之和都不可能等于0了。直接返回result。(注意:a可以等于0,例如0,0,0三数之和)

2. 对a去重

对a进行去重就是使a[i]!=a[i-1]。

为什么是和a[i-1]进行比较呢?

因为如果对a[i] = a[i+1] 就进行跳过的话,会排除掉{-1,-1,2}这种前两个数一致的情况。

注意:if(nums[i]!=nums[i-1]{........}不能这样写去重。因为i = 0时,i-1会造成数组越界,所以要加上i>0的条件:

// 正确去重a方法
if (i > 0 && nums[i] == nums[i - 1]) {
    continue;
}

3. 对b,c去重

while(left<right&&nums[left]==nums[left+1]) left++;//b去重 while(left<right&&nums[right]==nums[right-1]) right--;//c去重

不管b和c怎么挪,while循环里都别忘了加上left<right;

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result;
        sort(nums.begin(),nums.end());
        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;
            while(left<right){
                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(left<right&&nums[left]==nums[left+1]) left++;//b去重
                    while(left<right&&nums[right]==nums[right-1]) right--;//c去重
                    //找到结果两边都需要移动
                    left++;
                    right--;
                }
            }
        }
        return result;
    }
};

ps:以上注意的点都是写完本道题第二天再写时忽略错误的点。 

18. 四数之和 

力扣18

四数之和,和三数之和是一个思路,都是使用双指针法, 只是在三叔之和的基础上再套一层for循环。

如果充分理解了三数之和的算法逻辑,会发现四数之和这道题的步骤简直和“三数之和”一模一样。

a b c d 这四个数,在已有的b c d代码上再套一个遍历a的for循环,在遍历a和b时都是先剪枝再去重。a b c d 对应k i left right

但是由于本题求解的是=target而不是=0,得考虑target<0的情况,不能直接像上题一样通过nums[k]>target来剪枝。想剪枝排除不可能的情况必须同时满足:

if (nums[k] > target && nums[k] >= 0) {
    break; // 这里使用break,统一通过最后的return返回
}

再对b进行二级剪枝时也是如此:

if (nums[k] + nums[i] > target && nums[k] + nums[i] >= 0) {
    break;
}

完整代码:

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]>=0&&nums[k]>target){//第一个数就已经大于target了,不可能符合条件了,直接返回
                break;
            }
            if(k>0&&nums[k]==nums[k-1]) continue;//给a去重
            for(int i=k+1;i<nums.size();i++){
                if(nums[i]+nums[k]>=0&&nums[k]+nums[i]>target) break;
                if(i>k+1&&nums[i]==nums[i-1]) continue;//给b去重
                int left = i+1;
                int right = nums.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{
                        result.push_back(vector<int>{nums[k],nums[i],nums[left],nums[right]});
                        while(left<right&&nums[left]==nums[left+1]) left++;//给c去重
                        while(left<right&&nums[right]==nums[right-1]) right--;//给d去重
                        left++;
                        right--;
                    }
                 }
            }
        }
        return result;
    }
};

感想

昨天第一次做这些题时只有第二题是自己做出来的,当时觉得“三数之和”与“四数之和”都很难,第二天再做肯定忘了大部分了。结果没想到,今天做三数之和时几乎都想起来了,只有少数几个细节没有注意到(已经加粗加深理解了)。四数之和昨天没理清的剪枝去重操作今天再做,也可以很顺利地和“三数之和”里的对应上了。

继续加油!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值