多数之和问题(nSum)

leetcode上的第一道题就是2Sum,但除了2Sum还有3Sum,4Sum问题,我估计以后还会出5Sum,6Sum等题,在这里用一个函数来解决所有nSum类型的问题。

1.2Sum

我们就先来解决2Sum问题,2Sum问题的核心技巧是排序+双指针,先对序列进行排序,然后使用左右指针从两端相向而行就行了。

```C++
vector<int> twoSum(vector<int>& nums, int target) {
        // 先对数组排序
        sort(nums.begin(), nums.end());
        // 左右指针
        int lo = 0, hi = nums.size() - 1;
        while (lo < hi) {
            int sum = nums[lo] + nums[hi];
            // 根据 sum 和 target 的比较,移动左右指针
            if (sum < target) {
                lo++;
            } else if (sum > target) {
                hi--;
            } else if (sum == target) {
                return {nums[lo], nums[hi]};
            }
        }
        return {};
```


这样就可以解决这个问题了,但是如果序列中有多读元素之和都等于target,那么我们怎么办?
我们可以在sum等于target的时候将左右指针同时移动。
 

```C++
vector<vector<int>> twoSumTarget(vector<int>& nums, int target {
        // 先对数组排序
        sort(nums.begin(), nums.end());
        vector<vector<int>> res;
        int lo = 0, hi = nums.size() - 1;
        while (lo < hi) {
            int sum = nums[lo] + nums[hi];
            // 根据 sum 和 target 的比较,移动左右指针
            if      (sum < target) lo++;
            else if (sum > target) hi--;
            else {
                res.push_back({lo, hi});
                lo++; hi--;
            }
        }
        return res;
    }
```

但是,这样实现会造成重复的结果,比如说 nums = [1,1,1,2,2,3,3], target = 4,得到的结果中 [1,3] 肯定会重复。为了解决这个问题,我们可以当sum等于target的时候跳过左右重复的元素。循环中可以改成这样。

```C++
while (lo < hi) {
        int sum = nums[lo] + nums[hi];
        // 记录索引 lo 和 hi 最初对应的值
        int left = nums[lo], right = nums[hi];
        if (sum < target)      lo++;
        else if (sum > target) hi--;
        else {
            res.push_back({left, right});
            // 跳过所有重复的元素
            while (lo < hi && nums[lo] == left) lo++;
            while (lo < hi && nums[hi] == right) hi--;
        }
    }
    }
```


受到启发if中的语句也可以这样改

```C++
while (lo < hi) {
        int sum = nums[lo] + nums[hi];
        int left = nums[lo], right = nums[hi];
        if (sum < target) {
            while (lo < hi && nums[lo] == left) lo++;
        } else if (sum > target) {
            while (lo < hi && nums[hi] == right) hi--;
        } else {
            res.push_back({left, right});
            while (lo < hi && nums[lo] == left) lo++;
            while (lo < hi && nums[hi] == right) hi--;
        }
    }
```


至此一个通用版的两数之和代码写出来了,排序时间复杂度O(Nlog N),while循环时间复杂度O(N)。所以总的时间复杂度O(Nlog N)。

2.3Sum

我们两数之和都已经解决了,解决三数之和只要将一个数固定了不就降维成了两数之和问题嘛,那我们怎么做?直接穷举就完事了,但需要注意的是,三数之和的结果也可能重复,我们知道两数之和已经保证不会重复,那我们只要让第一个数不重复就行。

```C++
vector<vector<int>> threeSumTarget(vector<int>& nums, int target) {
        // 数组得排个序
        sort(nums.begin(), nums.end());
        int n = nums.size();
        vector<vector<int>> res;
        // 穷举 threeSum 的第一个数
        for (int i = 0; i < n; i++) {
            // 对 target - nums[i] 计算 twoSum
            vector<vector<int>>
                    tuples = twoSumTarget(nums, i + 1, target - nums[i]);
            // 如果存在满足条件的二元组,再加上 nums[i] 就是结果三元组
            for (vector<int>& tuple : tuples) {
                tuple.push_back(nums[i]);
                res.push_back(tuple);
            }
            // 跳过第一个数字重复的情况,否则会出现重复结果
            while (i < n - 1 && nums[i] == nums[i + 1]) i++;
        }
        return res;
    }
```


3.nSum

都到这份上了,4Sum 完全就可以用相同的思路:穷举第一个数字,然后调用 3Sum 函数计算剩下三个数,最后组合出和为 target 的四元组。

聪明的小伙伴可能已经发现了,这不就是递归问题嘛,求nSum,对第一个数穷举,然后求(n-1)Sum。

```C++
/* 注意:调用这个函数之前一定要先给 nums 排序 */
    vector<vector<int>> nSumTarget(
            vector<int>& nums, int n, int start, int target) {

        int sz = nums.size();
        vector<vector<int>> res;
        // 至少是 2Sum,且数组大小不应该小于 n
        if (n < 2 || sz < n) return res;
        // 2Sum 是 base case
        if (n == 2) {
            // 双指针那一套操作
            int lo = start, hi = sz - 1;
            while (lo < hi) {
                int sum = nums[lo] + nums[hi];
                int left = nums[lo], right = nums[hi];
                if (sum < target) {
                    while (lo < hi && nums[lo] == left) lo++;
                } else if (sum > target) {
                    while (lo < hi && nums[hi] == right) hi--;
                } else {
                    res.push_back({left, right});
                    while (lo < hi && nums[lo] == left) lo++;
                    while (lo < hi && nums[hi] == right) hi--;
                }
            }
        } else {
            // n > 2 时,递归计算 (n-1)Sum 的结果
            for (int i = start; i < sz; i++) {
                vector<vector<int>>
                        sub = nSumTarget(nums, n - 1, i + 1, target - nums[i]);
                for (vector<int>& arr : sub) {
                    // (n-1)Sum 加上 nums[i] 就是 nSum
                    arr.push_back(nums[i]);
                    res.push_back(arr);
                }
                while (i < sz - 1 && nums[i] == nums[i + 1]) i++;
            }
        }
        return res;
    }
```


代码看起来很长,其实就是把前面的两种解法结合了一下,当n==2时是2Sum问题,当n>2时就是穷举第一个数,然后调用递归求(n-1)Sum问题。其中要注意的是调用此方法前对数组进行排序,如果在方法体内排序,递归会进行多次排序。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值