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

这一期的题我刷的心力交瘁,感觉题目很类似,但是就是做不出来,第三道卡了一会,后来自己模拟了一遍才理解了。

同时这一期的题,也告诉我们不是所有这种类型的题,用哈希算法来解都简单,后两种我是用指针法做的,当然哈希表也是能解出来的。


454. 四数相加 II - 力扣(LeetCode)https://leetcode.cn/problems/4sum-ii/我们第一道题是先做四数相加II,有的读者可能会有疑惑,我们为什么不能先做四数相加再做II呢?相信在文章的最后你能找到答案。

这道题的解题思路,还较为简单,题目大意就是在四个数组中,找到一个四元组相加的和为0,为什么说它较为简单呢?原因有两个它并不涉及到去重,也就是说四元组中元素可以重复,只要保证它们是来自于不同的数组就可以了。

大体思路为:用一个map哈希结构来保存前两个数组的元素之和的所有可能情况,那么问题来了我们为什么要用到map而不是其他的哈希结构呢?原因很简单,四个数组中元素个数不固定,相加大小也不固定,用数组可能会存不下,不用set的原因是因为我们需要统计前两个数组和相同的答案在哈希中出现了几次,所用map更适合,为什么要统计这个,在代码给出后我会进行更详细解释。

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

auto是c++的独特书写语法,在看完上述代码,相信大家应该对于为什么要使用map有了一点模糊的想法,下面我给大家详细解答一下。由于该题目,要求四元组的值不同,只要四元组各答案的下标没有和其他四元组一一对应的重复就可以,如果这样讲有点抽象,我来举例说明,当后两个数和为-5的时候说明我们应该在前两个数里面找到5来相结合,这显而易见,那么我们如果找出a[1]+b[2]=-5,a[2]+b[3]=-5,a[3]+b[3]=-5诸如此类的情况呢?这些构成四元组的答案,实际上都是合法的,这就是我们为什么要存储相同的数值不同的数组下标构成的答案的原因。


383. 赎金信 - 力扣(LeetCode)https://leetcode.cn/problems/ransom-note/这道题的思路和上一期的那道有效字母异位词思路差不太多,这道题是在一个字符串里找字符看能否拼成另一个字符串,字符只能用一次,和那道题的不同之处在于,上期的那道题我们两个字符串的字母构成要求是一样的,这道题仅仅需要用一个字符串里的全部或部分字符来构成另一个字符串,这就足够了。

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

代码依旧是简单的,只要知道思路,明确了思路也就好写代码了。


15. 三数之和 - 力扣(LeetCode)https://leetcode.cn/problems/3sum/这道题我认为思路上还是有点难度在的,重要的是去重的思路,这道题是使用指针来实现的,题目大意是在一个给定的数组内,寻找一个三元组的和来等于0,该题为什么可以应用于指针解法呢?一个重要的判断是因为,该题返回的是数组中的元素组合,元素组合相加等于0,而并非让我们返回下标,使用双指针思路,一定要注意排序,而返回数组下标则不能排序,会打乱下标顺序。

先给出代码

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        sort(nums.begin(),nums.end());vector<vector<int>>result;
        vector<int>path;
        for(int i=0;i<nums.size();i++){
            if(nums[i]>0)return result;
            if(i&&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
                {
                    path.push_back(nums[i]);path.push_back(nums[left]);
                    path.push_back(nums[right]);result.push_back(path);
                    
                    while(left<right&&(nums[left]==nums[left+1]))left++;
                    while(left<right&&(nums[right]==nums[right-1]))right--;
                     path.clear();
                 left++;right--;
                }
               
                
            }
        }
        return result;
    }
};

第一层循环里第一个if相当于剪枝操作,排序后的第一个数组元素如果大于0,那么说明该数组不可能有三元组相加等于0了,这是一个技巧可以帮助我们来过滤一些数组,提高运行效率。第二个if是帮助第一个循环里的i去重的,你取到的重复元素假设为1,那么这个1包含了你下个1之后的所有元素组合,他们的情况都是完全一样的,所以我们并不需要重复遍历。

第二个循环中代码核心主要是排序部分,起初left指向的是i的下一个位置,而right指向的是数组的最后一个元素,如果三个数相加和大于0那么将right向前挪动一位,如果小于0,那么就将left向后挪一位,这就体现了为什么使用指针法,一定要对数组进行排序!!当前面的调整完毕后,找到了一组数据我们将其放入结果数组中,之后就到了最关键的一步,用两个while循环来去重left和right指向的值,当left大于等于right了立马跳出,那么为什么之后我们还是需要进行left++和right--呢?不是只有left和right不跟相邻元素相等时才跳出来的吗?我们再进行left++和right的--操作会不会略过去一些答案呢??相信一些初学者也会和我有一样的疑问,让我们往下按看!

试想一下以下两种不同的情境:

第一种:没有相同的元素,当我们将答案中的一个三元组存放到结果数组后,由于没有数字重复,无法通过循环来调整left和right位置那么left和right不能得到更新会使循环陷入死循环。

第二种情况:存在相同的元素,在存放一个三元组后,我们发现left或者right相邻值有重复的情况,亦或是两者都存在相邻值与之前答案的元素相同的情况,那么我们直接进入while循环,重点来了,在循环内我们是判断left和left++,right和right--的数有没有出现重复的情况,这样看来,我们结束循环跳出来之后的left和right指向了我们重复元素的最后一次重复的地方,因为它的确不和下一个我们要判断的数字相等,所以跳了出来,如果没有left++和right--来调整,那么我们还会遍历到之前答案的数,这也就是为什么我们一定要有left++和right--的缘故。

那么可不可以将left++和right--放到前面呢?答案也是可以的,如果大家觉得先判断去重,再缩小范围的思路有点怪,也可以先left++和right--调整搜索范围,再对left和right进行一个去重的判断,只不过那样我们的去重逻辑有一点小变化,这时你需要将left和它的前一个作比较,right和它的后一个作比较,理由也是和上一个思路一样的,为了避免重复遍历答案,目的是直接将此时的left和right直接置到和上一个答案三元组不同的元素中,不明白的读者可以在纸上模拟一下,思路自然会清晰!


18. 四数之和 - 力扣(LeetCode)icon-default.png?t=M85Bhttps://leetcode.cn/problems/4sum/这道题的解题思路和上一道的三数之和思路十分类似,这也正是我们把这道题放在最后的原因,题目大意是给你一个数组,要求返回若干四元组和为target,在剪枝方面与上一题有所不同,因为上一题是总和等于0,那么排序后第一个元素大于0,直接剪枝,但是这道题是要求四元组和构成target,而target可能是负数,所以不能单纯判断其大于0,那么可不可以单纯判断nums【i】>target呢?也是不可以的,因为如果第一个元素是大于target的负数,但是第二个元素也是一个负数,那说不定就落下一个答案了。这些细节我们都需要注意,先看代码

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)break;
        if(k&&nums[k]==nums[k-1])continue;
        for(int i=k+1;i<nums.size();i++){
            if(nums[k]+nums[i]>=0&&nums[k]+nums[i]>target)break;
            if(i>k+1&&nums[i]==nums[i-1])continue;
            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(right>left&&nums[left]==nums[left+1])left++;
                    while(right>left&&nums[right]==nums[right-1])right--;
                    left++;right--;
                }
            }
        }
    }
    return result;
    }
};

代码不算短,有很多嵌套逻辑,但是整体和三数之和是一样的,只不过四数之和多了一层循环仅此而已,同样的前两层可以做适当的剪枝操作,每一层都需要进行判重,防止四元组重复,

需要注意的点在于,剪枝操作和三数之和有所不同,最后一层判断时,要加上强制类型转换为long类型,以防止数据和过大,int放不下。


以上代码均可ac,有用望支持

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学习算法的杨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值