【从零开始】一次解决n数之和

在刚开始进入力扣时,我们可能会遇到刷题时期的第一道题,也是力扣的001号,两数之和。

对于当时连vector是什么都不知道的我,使用暴力解法通过的成就感也是无可比拟的。

在经历了后面的三数之和,四数之和的冲击后,发现这类题都是双指针的表现舞台。

两数之和

leetcode 1 两数之和

给定一个数组和一个目标值。找到和为目标值的两个整数,返回两个数值。

注意我们这里调整了题目,返回的是数值。(后面说明区别)

暴力/哈希解法

暴力解法是比较好想的,两个for循环去找和为目标值的那一组。

哈希解法可以降低时间复杂度。循环每一个x,去哈希表中找target-x是否存在。

但这些方法在三数、四数时会变得复杂,所以不是我们思考的重点。

排序+双指针

既然是一个数组中找两个数,我们很自然地想到双指针。一个从左边走,另一个从右边走,然后判断它们的和。

需要注意的是数组需要有序才能从头尾开始,这样在大于target或者小于target时才知道该移动哪边的指针。

在排序之后,左右指针就可以移动了。

因为没有重复的元素,所以不需要去重。只需要在和大于target时缩小右指针(想让总和小一点),和小于target时扩大左指针(想让总和大一点)

vector<int> twoSum(vector<int>& nums, int target) {
        sort(nums.begin(),nums.end());
        int left=0;
        int right=nums.size()-1;

        while(left<right){
            int sum=nums[left]+nums[right];
            if(sum<target){
                left++;
            }
            else if(sum>target){
                right--;
            }
            else
                return {nums[left],nums[right]};
        }
        return {};

    }

代码比较简单。

去重

**那如果有重复元素呢?**比如排序后[1,1,1,2,2,3,3],target=3时,如果不考虑重复则会输出多组[1,2]。

(本题遇到一个结果就返回了,如果需要返回多个结果就需要去重)

所以去重也是我们需要掌握的点(三数、四数都有)

其实去重对于排序数组来说比较简单:

  • left指针只要考虑它的后一位,如果相等就++
  • right指针只要考虑它的前一位,如果相等就–

而且去重这一步可以在找到结果后进行

vector<int> twoSum(vector<int>& nums, int target) {
        sort(nums.begin(),nums.end());
    	vector<vector<int>>res;
        int left=0;
        int right=nums.size()-1;

        while(left<right){
            int sum=nums[left]+nums[right];
            if(sum<target){
                left++;
                while(left<right&&nums[left]==nums[left-1])	left++;
            }
            else if(sum>target){
                right--;
                while(left<right&&nums[right]==nums[right+1]) right--;
            }
            else{
             	res.push_back( {nums[left],nums[right]});
           		while(left<right&&nums[left]==nums[left+1])	left++;//去重
            	while(left<right&&nums[right]==nums[right-1]) right--;//去重
            
            	right--;
            	left++;
            }
        }
        return {};

    }

与上面的情况的区别在于:

  • 找到答案存入vector后,进行去重操作
  • 因为答案不唯一,所以指针还需要收缩移动到下一位

在sum>target和sum<target时也可以加入去重操作,当然不加也可以。

为了严谨,我们这部分去重也加上。

需要注意,当结果不匹配时,left和right已经移动了,我们去重是要比较前面走过的。

当结果匹配时,我们去重要比较的是left和right的下一位。

三数之和

掌握了上面的去重双指针方法之后,N数之和都可以轻松拿下了。

先来看三数

leetcode 15 三数之和

判断哪三个数的和为0。这里特例化为0,其实也可以是target

我们的思路依旧是排序+双指针。

不过这里是三个数,所以我们先用for循环控制一个数,再去使用左右指针。

看代码就会明白这种思路,就是在for循环里加入左右指针的while循环。

vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>>res;
        sort(nums.begin(),nums.end());
        for(int i=0;i<nums.size();i++){
            if(nums[i]>0)//如果排序后第一个大于0,那么和一定不为0
                return res;
            
            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--;
                    while(right>left&&nums[right]==nums[right+1])
                        right--;
                }
                else if(nums[i]+nums[left]+nums[right]<0){
                    left++;
                    while(right>left&&nums[left]==nums[left-1])
                        left++;
                }
                else{
                    res.push_back({nums[i],nums[left],nums[right]});
                    while(right>left&&nums[right]==nums[right-1])
                        right--;
                    while(right>left&&nums[left]==nums[left+1])
                        left++;
                    left++;
                    right--;
                }
            }
        }
        return res;


    }

后面的过程和我们之前的两数模板一样。

for循环中有三处需要注意的地方:

  • if(nums[i]>0)这里,是因为我们的target比较特殊,刚好是0,所以可以这样判断。但如果target是任意值就不可以了。

  • 去重操作if(i>0&&nums[i]==nums[i-1]),一定要注意去重 要和前一个比,不能和后一个比,因为和后一个还没有遍历到的值比就可能会漏掉结果。

  • left开始的位置不是0,是i的下一个,因为三个数分别是nums[i],nums[left],nums[right]

四数之和

leetcode 18 四数之和

同理,四数之和我们也可以轻松解决。只需要先控制两个数进行for循环嵌套,剩下的两个数依旧使用左右指针。

vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>>res;
        sort(nums.begin(),nums.end());
        for(int i=0;i<nums.size();i++){
            if(i>0&&nums[i]==nums[i-1]){//去重
                continue;
            }
            
            for(int k=i+1;k<nums.size();k++){
                if(k>i+1&&nums[k]==nums[k-1]){//去重
                continue;
            }
            int left=k+1;
            int right=nums.size()-1;
            while(left<right){
                if(nums[i]+nums[k]>target-(nums[left]+nums[right])){
                    right--;
                    while(right>left&&nums[right]==nums[right+1])
                        right--;
                }
                else if(nums[i]+nums[k]<target-(nums[left]+nums[right])){
                    left++;
                    while(right>left&&nums[left]==nums[left-1])
                        left++;
                }
                else{
                    res.push_back({nums[i],nums[k],nums[left],nums[right]});
                    while(right>left&&nums[right]==nums[right-1])
                        right--;
                    while(right>left&&nums[left]==nums[left+1])
                        left++;
                    left++;
                    right--;
                }
            }
        }
            
    }
        return res;

    }

代码看上去有点长,但实际步骤还是一样的。

nums[i]+nums[k]>target-(nums[left]+nums[right])这步的处理方法是因为如果将四者相加有可能会溢出,所以需要做一下减法。

复杂度

如果我们使用暴力解法,那么对于三数之和来说,就是三个for循环。时间复杂度就是O(n的三次方)

但是使用双指针后可以减少一个for循环,而内部的left和right其实就是一个O(n)的复杂度。

所以时间复杂度是O(n的平方)(排序复杂度O(nlogn),小于平方)

空间复杂度因为我们修改了数组(排序),所以相当于存储了副本,为O(n)

同理,四数之和也就是多了一个量级,时间复杂度为O(n的三次方)

四数之和2

有一道非常像四数之和的题目

leetcode 454 四数之和2

给定四个整数数组,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0

其实这里就是要找四个数相加为0,但是不考虑重复的问题,所以用哈希表就可以完美解决。

因为最后返回的是个数,所以我们需要unordered_map来统计和以及对应出现的次数

int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        unordered_map<int,int>myMap;
        for(int a:nums1)
        {
            for(int b:nums2)
            {
                myMap[a+b]++;//记录a+b的和以及出现次数
            }
        }
        int count=0;
        for(int c:nums3)
        {
            for(int d:nums4)
            {
                if(myMap.count(0-(c+d)))//寻找互补,找到就记录次数。
                {
                    count+=myMap[0-(c+d)];//
                }
            }
        }
        return count;

    }

回到两数之和

我们在开篇说到了两数之和,并且对问题做了一些修改,返回了数值。

而原题其实是返回下标的。

在返回下标时,我们就不能使用双指针法了,因为排序破坏了原有的顺序

这时候使用哈希表可以降低复杂度。

因为结果要输出下标,所以我们需要unordered_map对应这个数以及它的下标。

锁定i,然后寻找target-nums[i]。

如果没有,把当前的数和下标存入。

如果有,返回找到的这个数的下标以及当前的i,就是最后需要的下标数组

vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int,int>res;
        for(int i=0;i<nums.size();i++){
            if(res.count(target-nums[i])){ 
                auto ite=res.find(target-nums[i]);
                return {ite->second,i};
            }
            res.insert(pair<int,int>(nums[i],i));
        }

        return {};
    }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用c语言解决【问题描述】 卡卡同学最近开始学习的加法运算,一天老师为了测试大家对加法的 掌握情况,让同学们做了一个有趣的加法接力赛游戏。老师在黑板上从左到 右写下了 n 个大于零的整,老师接下来找出 n 个同学完成下面的计算任务。第一个同学在黑板上抄下最左边的字,第二个同学将第一个同学写下的 字加上老师写的第二个字所得到的和写在黑板上。以此类推,第 m 个同学 将前面第 m-1 个同学所写下的字加上老师在黑板上写下的第 m 个字所得 之和,并将结果写到黑板上。当计算完成这 n 个字之后,老师又让同学从 最右边开始按相同的方法再次计算出 n 个值。最后老师让同学们找出这所 有字当中共有多少个是相同的。例如:老师在黑板上写下了 7 个,从左到右依次为:3,6,2,1,4, 5,2,则同学们第一次从左边开始计算所得到的 7 个值应该是:3,9,11, 12,16,21,23;而第二次从右边开始计算所得到的 7 个值应该是:2,7, 11,12,14,20,23;于是第一次与第二次计算出的值中相同的有 3 个, 它们分别是:11、12 与 23。【输入】 共两行,第一行为 n(1<n<100),第二行为 n 个,用空格隔开, 每个 的大小在 0~200 之间。【输出】 第一次与第二次计算出的值中相同的个。【输入样例】 7 3 6 2 1 4 5 2 【输出样例】 3
05-24

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值