前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >四数之和(详细题解:双指针+排序)

四数之和(详细题解:双指针+排序)

作者头像
利刃大大
发布2023-04-12 14:31:38
2090
发布2023-04-12 14:31:38
举报
18. 四数之和

难度中等1502

给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):

  • 0 <= a, b, c, d < n
  • abcd 互不相同
  • nums[a] + nums[b] + nums[c] + nums[d] == target

你可以按 任意顺序 返回答案 。

示例 1:

代码语言:javascript
复制
输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]

示例 2:

代码语言:javascript
复制
输入:nums = [2,2,2,2,2], target = 8
输出:[[2,2,2,2]]

提示:

  • 1 <= nums.length <= 200
  • -109 <= nums[i] <= 109
  • -109 <= target <= 109

解题思路:排序+双指针

这道题其实就是 15. 三数之和 的拓展,本质上还是一样的,只不过剪枝和去重的细节又多了一点!

​ 首先一开始还是排序,这个问题我们已经在三数之和那道题中说过了,具体的可以去翻阅笔记查看!

​ 接着就是遍历 nums,因为这次是四个数,双指针分别占了两个,所以我们在最外层的循环还需要套两层,比三数之和多了一层循环,最重要的是多了一层循环,也就是多了一个剪枝和去重工作!但是大体步骤和三数之和是类似的!

1、先来讲讲最外层这个 i 的剪枝和去重工作:

​ 这道题三数之和不太一样,这道题要求四个数的和不能超过 target,那么我们在最外层的剪枝判断中能不能说直接 nums[i] > target 就剪枝呢?肯定是不行的,因为 target 可能是负数,并且 nums[i] 以及 nums[i] 后面的数也可能是负数,这个时候一个数加上负数,它们的和反而是会变小的,所以 不能直接判断是否 nums[i] > target 就剪枝!举个例子,比如 {-4, -1, 0, 0} 数组中我们要找的是和为 -5 的,那么这个数组刚刚好就是一个解,但是如果我们如果直接判断 nums[i] > target 也就是 -4 > -5 后剪枝,那么就错过了这个解,那么就错了!

​ 所以最外层的剪枝工作是需要修改一下的,我们还需要判断 nums[i] 是否大于等于0,因为我们的数组是排序过的,这样子就能避免 nums[i] 后面加的数是负数,所以正确的剪枝判断是: if(nums[i] > target && nums[i] >= 0) break;

​ 接下来就是最外层的去重操作,这个和三数之和是一模一样的,只要判断 nums[i]nums[i - 1] 是否相同,是的话就说明重复了,直接 continue,除此之外 还需要注意边界问题就是需要让 i > 0 防止 nums[i -1] 索引越界!具体的原因可以参考三数之和的笔记!

2、接下来讲一下第二层 j 的剪枝和去重工作:

​ 首先 j 肯定是通过最外层的 i + 1 来开始遍历的,所以对于去重工作,都是类似的,只是这里不是 nums[j] > 0而是 nums[j] > i + 1 来进行防止索引越界,并且只要 nums[j] == nums[j - 1] 我们就直接 continue 即可!

​ 对于第二层的剪枝,不只是单单的判断 nums[j] > target 就剪枝,因为有可能我们最外层的 nums[i] 就是一个负数,而 nums[i] + nums[j] 之后得到的数比 target 还小,那么这就不能剪枝了,所以我们要考虑到最外层的情况!

正确的剪枝操作:if(nums[i] + nums[j] > target && nums[i] >= 0 && nums[j] >= 0) continue;

​ 上述剪枝操作也可以进行一下化简:if(nums[i] + nums[j] > target && nums[i] >= 0) continue;

​ 这个化简就是简单的数学问题了,要求 nums[i] >= 0,因为数组的有序的,所以 nums[j] 肯定也是 >= 0 的,所以就没必要判断 j了!

3、最后说一下双指针的移动

​ 这个大概操作和三数之和是一样的!但是有一些细节问题,就是直接是三个数与 target 比较,现在要变成四个数!

​ 除此之外,我们四个数相加,对于这道题的数据来说是会发生溢出的,所以我们在相加的时候需要将它们的类型转化为 long 类型以上才行,而对于类型转化表达式,我们只需要对其中一个变量进行类型转化,其它的三个变量会根据不同类型进行隐式类型转化的,也就是一个类型是 long,其它类型是 int 的话,最后相加的结果还是 long!这样子就不会溢出了!

​ 其它问题就和三数之和是一样的!

代码语言:javascript
复制
class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        sort(nums.begin(), nums.end()); // 别忘了排序!

        vector<vector<int>> vv;
        int n = nums.size();
        for(int i = 0; i < nums.size(); ++i)
        {
            // 进行一级剪枝和一级去重
            // 剪枝中注意如果target是负数的话且nums[i]>target,那么直接不能剪枝
            // 因为虽然nums[i]>target,但是nums[i]加上nums[j]不一定比target大,所以要排除nums[i]小于0的情况!
            if(nums[i] > target && nums[i] >= 0)
                break;
            if(i > 0 && nums[i] == nums[i - 1])
                continue;

            for(int j = i + 1; j < nums.size(); ++j)
            {
                // 进行二级剪枝和二级去重
                // 注意这里剪枝的时候不只是判断nums[j]>target,因为有可能nums[i]是负数,这样子它们两个数相加之后可能会比target小
                // 这里写成if(nums[i] + nums[j] > target && nums[i] >= 0) 也是可以的,数学问题!
                if(nums[i] + nums[j] > target && nums[i] + nums[j] >= 0)
                    break;

                // 去重时候如果前一个是i的话,那么相当于j是i这一轮的第一遍执行,所以不需要continue
                if(j > i + 1 && nums[j] == nums[j - 1])
                    continue;

                int left = j + 1, right = n - 1;
                while(left < right)
                {
                    // 四个数加起来可能会溢出,所以要先变成long类型
                    if((long) nums[i] + nums[j] + nums[left] + nums[right] > target)
                        right--;
                    else if((long) nums[i] + nums[j] + nums[left] + nums[right] < target)
                        left++;
                    else
                    {
                        vv.push_back(vector<int>{nums[i], nums[j], nums[left], nums[right]});

                        // left和right的去重
                        while(left < right && nums[right] == nums[right - 1])
                            right--;
                        while(left < right && nums[left] == nums[left + 1])
                            left++;
                        
                        // 别忘了再更新一次left和right才是不重复的那个值
                        left++;
                        right--;
                    }
                }
            }
        }
        return vv;
    }
};
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-02-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 解题思路:排序+双指针
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档