双指针 | | 求数组和为 s 的两个数及扩展

目录

两数之和

有效三角形的个数

三数之和

四数之和


两数之和

LCR 179. 查找总价格为目标值的两个商品 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/he-wei-sde-liang-ge-shu-zi-lcof/description/

 这道题本质上是找出数组中任意两数之和为 target,我们可以使用两层 for 循环暴力的找出这两个数,但这样会超时。

可以注意到,题目提供的数组是升序的,升序的数组中,最小值在数组的最左边,最大值在数组的最右边,假设 left 为数组的左端,初始化为 0,right 为数组的右端,初始化为 price.size() -1,

以示例2为例,在暴力解法中,比如先固定8,接着 for 循环遍历 8 后面的数字,一个个求和,看它们的和是否等于 target,但是我们没必要一个个求和,

1、如下图,如果 price[ left ] + price[ right ] > target ,说明 price[ right ] 和 比 price[ left ] 大的数的和一定大于 target,那么在升序数组中, price[ left ] 后面的数所以就没有必要一个个遍历并与 price[ right ] 求和了,此时 --right,去比较下一组数

2、如果 price[ left ] + price[ right ] < target ,和上面同理,price[ left ] 和比 price[ right ] 小的数的和一定小于 target,此时 ++left,去比较下一组数

3、以此类推,直到 price[ left ] + price[ right ] = target 为止

4、需要注意,left 必须小于 right,否则数组里的数重复遍历了,如果 left 和 right 相遇了,说明这个数组里面没有两数之和为 target

class Solution {
public:
    vector<int> twoSum(vector<int>& price, int target) {
        int left=0;
        int right=price.size()-1;
        vector<int> v;
        while(left<right)
        {
            if(price[left]+price[right]>target)
                --right;
            else if(price[left]+price[right]<target)
                ++left;
            else
            {
                v.push_back(price[left]);
                v.push_back(price[right]);
                break;
            }
        }
        return v;
    }
};

有效三角形的个数

611. 有效三角形的个数 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/valid-triangle-number/description/

 假设三角形的三条边分别为 a , b , c,三角形成立的条件为 a+b > c ,a+c>b , b+c>a 。

我们需要经过三次判断,才可以看出  a , b , c 是否构成三角形,如何减少判断的次数?

假设 a , b , c 已经升序排序,a 为最小值,c 为最大值,则一定有 c>a , c>b ,而 a , b , c 都是正数,c + 正数 > a, c + 正数 >b 显然成立,那么,这时候只需要判断 a+b> c是否成立,就可以判断出 a , b , c 能否构成三角形,此时只需要 1次排序 + 1次判断

如图,在升序数组中,假设最大值 5 为 c,nums[ left ] 为 a , nums[ left ] 为 b,和两数之和同理,不同的是,这一次需要寻找比 target 大的两个数

1、如果 nums[ left ] + nums[ right ] <= c,则这三个数不构成三角形,此时 ++left,

2、如果 nums[ left ] + nums[ right ] > c,则这三个数构成三角形,比 nums[ left ] 大的数和 nums[ right ] 相加一定大于 c,此时能构成三角形的三元组个数恰好为 right - left ([2,4,5](第一个2),[2,4,5](第二个2),[3,4,5]),此时 --right,去寻找其他的三元组

class Solution {
public:
    int triangleNumber(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        int ret=0;//记录个数
        for(int i=nums.size()-1;i>1;--i)
        {
            int left=0;
            int right=i-1;
            while(left<right)
            {
                if(nums[left]+nums[right]>nums[i])//构成三角形
                {
                    ret+=(right-left);
                    --right;
                }
                else//不构成三角形
                    ++left;
            }
        }
        return ret;
    }
};

 三数之和

15. 三数之和 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/3sum/description/

和有效三角形的个数同理,这次我们需要找的是两数之和为 -target 的数,而且这三元组之间不可以重复(示例1)。

以示例1为例:对示例1的数组进行排序后,先以最左端的元素开始,那么 target 为 4,我们需要找出什么时候 nums[ left ] 和 nums[ right ] 的和为 target,

1、如果  nums[ left ] + nums[ right ] > target,--right

2、如果  nums[ left ] + nums[ right ] < target,++left

如果两数之和一直不等于 target,但是 left = right,那么说明数组中不存在两数之和为 target,需要换新的 target 了

3、如果 nums[ left ] + nums[ right ] = target,那么把此时访问的三个数存储在vector中,此时 left 和 right 都需要更新( ++left , --right )

如果left/right更新之后,仍然和上一次访问的 nums[ left ] / nums[ right ] 相同,因为题目条件,三元组不可以重复,所以需要继续更新 left / right,直到和上一次访问的nums[ left ] / nums[ right ] 不同为止

 

4、更新 target 之后,和上次的 target 相同,这时候再去找两数之和等于 target ,就会出现重复的三元组,所以需要迭代至和上一个 target 不同为止

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        sort(nums.begin(),nums.end());//排序
        int i=0;
        vector<vector<int>> vv;//返回值
        while(i<nums.size() && nums[i]<=0)
        {
            int left=i+1;
            int right=nums.size()-1;
            while(left<right)
            {
                if(nums[left]+nums[right]+nums[i]>0)
                {
                   --right;
                }
                else if(nums[left]+nums[right]+nums[i]<0)
                {
                    ++left;
                }
                else
                {
                    vv.push_back({nums[left],nums[right],nums[i]});
                    ++left;
                    --right;
                    //去重左指针
                    while(left<right && nums[right]==nums[right+1]) { --right;}
                    //去重右指针
                    while(left<right && nums[left]==nums[left-1]) {++left; }
                }
            }
            //去重target
            i++;
            while(i<nums.size() && nums[i]==nums[i-1]) {++i; }
        }
        return vv;
    }
};

四数之和

18. 四数之和 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/4sum/description/

四数之和可以划分为三数之和、两数之和,

比如先固定 - 4,问题变成在红色框的数中找三数之和为 target1 = target -(- 4) ,

在找三数之和时,同样固定 -4,问题变成在蓝色框的数中找两数之和为 target2 = target1 -(- 4),

在求解四数之和时,同样需要和三数求和一样,去掉重复的解

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        sort(nums.begin(),nums.end());
        vector<vector<int>> vv;
        for(int i=0;i<nums.size();)
        {
            long long target1=(long long)target-nums[i];
            //三数之和为target1
            for(int j=i+1;j<nums.size();)
            {
                long long target2=(long long)target1-nums[j];
                //两数之和为target2                
                int left=j+1;
                int right=nums.size()-1;
                while(left<right)
                {
                    if(nums[left]+nums[right]>target2) --right;
                    else if(nums[left]+nums[right]<target2) ++left;
                    else{
                        vv.push_back({nums[i],nums[j],nums[left++],nums[right--]});
                        //去重左指针
                        while(left<right && nums[left]==nums[left-1]) ++left;
                        //去重右指针
                        while(left<right && nums[right]==nums[right+1]) --right;
                    }
                }
                //去重target2 
                ++j;
                while(j<nums.size() && nums[j]==nums[j-1]) ++j;
            }
            //去重target1
            ++i;
            while(i<nums.size() && nums[i]==nums[i-1]) ++i;
        }
        return vv;
    }
};

  • 30
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值