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

一. LeetCode454题.四数相加II

题目链接/文章讲解/视频讲解:https://programmercarl.com/0454.%E5%9B%9B%E6%95%B0%E7%9B%B8%E5%8A%A0II.html

状态:已解决

1.思路 

        这道题还是思路还是很简单,将公式A[i] + B[j] + C[k] + D[l] = 0变形为:A[i] + B[j] = -(C[k] + D[l])即可。由于题目是求满足的条件元组 (i, j, k, l),因此不仅需要关注元素的值,也需要关注元素值出现的位置,故该题不应该去重,值相等的两个元素要算成两种不同的情况。最开始我用的set做,只保存了前两个数组的和的值,没有计算同个和值的个数,导致错误,因此为了得到同样的和值的个数,我们必须要用map的数据结构来实现这道题。

解题步骤:

(1)首先定义unordered_map数组(效率高),key存放a+b的值,value存放该和值出现的次数。

(2)遍历A数组和B数组,统计两个数组元素之和,和出现的次数,放到map中。

(3)用一个int变量sum存放满足等式的元组个数。

(4)遍历C数组和D数组,看map中是否有等于-(c+d)的值,若有,sum就加上该和值出现的次数value。

(5)返回sum。

2.代码实现 

        我特别傻地还存了一遍c+d的和,然后再进行了一次遍历查找map2中有没有map1需要的值,不仅耗时还耗内存,我真傻,真的。

class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        unordered_map<int,int> s1,s2;
        long long int sum = 0;
        for(int i=0;i<nums1.size();i++){
            for(int j=0;j<nums2.size();j++){
                auto iter = s1.find(nums1[i]+nums2[j]);
                if(iter != s1.end()){
                    iter->second += 1;
                }else{
                    s1.emplace(nums1[i]+nums2[j],1);
                }
            }
        }
        for(int i=0;i<nums3.size();i++){
            for(int j=0;j<nums4.size();j++){
                auto iter = s2.find(nums3[i]+nums4[j]);
                if(iter != s2.end()){
                    iter->second += 1;
                }else{
                    s2.emplace(nums3[i]+nums4[j],1);
                }
            }
        }
        for(auto iter1:s1){
            int num = iter1.first;
            auto iter2 = s2.find(-num);
            if( iter2!= s2.end()) sum+=(iter1.second)*(iter2->second);
        }
        return sum;
    }
};

根据视频讲解改进后的:

        不仅学会了更快遍历vector容器的方法,还学到了直接用和作为索引访问map的技巧。

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

时间复杂度:O(n^2)

空间复杂度: O(n^2),最坏情况下A和B的值各不相同,相加产生的数字个数为 n^2

二. LeetCode 383. 赎金信

题目链接/文章讲解:https://programmercarl.com/0383.%E8%B5%8E%E9%87%91%E4%BF%A1.html

状态:已解决

1.思路 

        这道题跟昨天的242一样的思路,只是判断条件不一样,因为242有效字母异位词要求两个字符串长度相等且字母出现个数相同,而这道题只需要magazines里面的字符比ransom的相同字符多,前者可以构成后者即可。

(具体分析思路可以看昨天的做题链接:LeetCode242.有效的字母异位词

2.代码实现

   (看了文章介绍才发现可以直接在第二个循环里面就判断,反正发现了某个元素value小于0就返回)。

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        unordered_map<int,int> s;
        for(auto r:ransomNote){
            s[r-'a']++;
        }
        for(auto m:magazine){
            if(s.find(m-'a')!=s.end())
                s[m-'a']--;
        }
        for(auto iter:s){
            if(iter.second > 0) return false;
        }
        return true;
    }
};

时间复杂度:O(n)

空间复杂度:O(1)

三. LeetCode15. 三数之和

题目链接/文章讲解/视频讲解:https://programmercarl.com/0015.%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C.html

状态:已解决

1.思路 

        这道题用哈希法较难,不太适合,准备二刷再用哈希做。

        讲解用的双指针,很妙。

        拿这个nums数组来举例,首先将数组排序,然后有一层for循环,i从下标0的地方开始,同时定一个下标left 定义在i+1的位置上,定义下标right 在数组结尾的位置上。

依然还是在数组中找到 abc 使得a + b +c =0,我们这里相当于 a = nums[i],b = nums[left],c = nums[right]。接下来如何移动left 和right呢, 如果nums[i] + nums[left] + nums[right] > 0 就说明 此时三数之和大了,因为数组是排序后了,所以right下标就应该向左移动,这样才能让三数之和小一些。如果 nums[i] + nums[left] + nums[right] < 0 说明 此时 三数之和小了,left 就向右移动,才能让三数之和大一些,直到left与right相遇为止。

        一个很重要的点是如何去重:对于nums[i],由于它是真正去遍历一次数组的部分,即每轮的最终起点,left和right都是根据i开启循环遍历的,因此它最先去重。去的方法也很简单,假如nums[i]==nums[i-1],就跳过,因为该值在第i个元素那里就已经做过后续循环遍历了。不能是nums[i]==nums[i+1],因为有可能数组包含三个连续的0,那么这种去重就直接到最后一个0去了,无法得到(0,0,0)的解。

        left和right的去重很好理解,但不要忘了最后还要再更新一下left、right的值,因此while结束代表此时还在重复值中,left和right的下一个值才不在重复值之中。

2.代码实现 

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) break;
            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){
                    left++;
                }
                else if(nums[i]+nums[left]+nums[right]>0){
                    right--;
                }
                else{
                    result.push_back(vector<int>{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的下一个值才不在重复之中。
                    left++;
                    right--;
                }
            }
        }
        return result;
    }
};

时间复杂度:O(n^2)

空间复杂度:O(1) 

四. LeetCode 18. 四数之和

题目链接/文章讲解/视频讲解:https://programmercarl.com/0018.%E5%9B%9B%E6%95%B0%E4%B9%8B%E5%92%8C.html

状态:已解决

1.思路

        与三数之和思路差不多,依旧是双指针的做法,区别在于四数之和还要在最外面再套一层循环,此时以最外层的k作为每次循环的起点,代表第四个数,然后第i个数从它右侧开始遍历,left和right在i和k指向元素之和的基础上做移动。

2.代码实现

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) break;//一级剪枝
            if(k > 0 && nums[k]==nums[k-1]) continue;//一级去重
            for(int i=k+1;i<nums.size();i++){
                if((nums[k]+nums[i])>target && nums[k]+nums[i]>=0) 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){
                        left++;
                    }
                    else if((long)nums[k]+nums[i]+nums[left]+nums[right]>target){
                        right--;
                    }
                    else{
                        result.push_back(vector<int>{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的下一个值才不在重复之中。
                        left++;
                        right--;
                    }
                }
            }
            
        }
        return result;
    }
};

时间复杂度:O(n^3) (双指针法就是将原本暴力O(n^3)的解法,降为O(n^2)的解法,四数之和的双指针解法就是将原本暴力O(n^4)的解法,降为O(n^3)的解法)

空间复杂度:O(1) 

五、总结 

        不在同一个数组,而是在几个独立的数组中找元素之和为固定值的元组, 采用哈希法更简单。同一数组中就用双指针将时间复杂度降低一个数量级。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值