Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[LeetCode] 18. 4Sum #18

Open
grandyang opened this issue May 30, 2019 · 2 comments
Open

[LeetCode] 18. 4Sum #18

grandyang opened this issue May 30, 2019 · 2 comments

Comments

@grandyang
Copy link
Owner

grandyang commented May 30, 2019


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Given an array nums of n integers, return an array of all the unique quadruplets [nums[a], nums[b], nums[c], nums[d]] such that:

  • 0 <= a, b, c, d < n
  • a, b, c, and d are distinct.
  • nums[a] + nums[b] + nums[c] + nums[d] == target

You may return the answer in any order.

Example 1:

**Input:** nums = [1,0,-1,0,-2,2], target = 0
**Output:** [[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]

Example 2:

**Input:** nums = [2,2,2,2,2], target = 8
**Output:** [[2,2,2,2]] 

Constraints:

  • 1 <= nums.length <= 200
  • -10^9 <= nums[i] <= 10^9
  • -10^9 <= target <= 10^9

这道题给了一个整型数数组 nums 和一个目标值 target,让找出所有的四个数字的组合,使得其和等于给定的目标值 target。LeetCode 中关于数字之和还有其他几道,分别是 Two Sum3Sum3Sum Closest 等等,虽然难度在递增,但是整体的套路都是一样的,在这里为了避免重复项,我们使用了 STL 中的 TreeSet,其特点是不能有重复,如果新加入的数在 TreeSet 中原本就存在的话,插入操作就会失败,这样能很好的避免的重复项的存在。此题的 O(n^3) 解法的思路跟 3Sum 基本没啥区别,就是多加了一层 for 循环,其他的都一样,代码如下:

解法一:

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        set<vector<int>> res;
        sort(nums.begin(), nums.end());
        for (int i = 0; i < int(nums.size() - 3); ++i) {
            for (int j = i + 1; j < int(nums.size() - 2); ++j) {
                if (j > i + 1 && nums[j] == nums[j - 1]) continue;
                int left = j + 1, right = nums.size() - 1;
                while (left < right) {
                    long sum = (long)nums[i] + nums[j] + nums[left] + nums[right];
                    if (sum == target) {
                        vector<int> out{nums[i], nums[j], nums[left], nums[right]};
                        res.insert(out);
                        ++left; --right;
                    } else if (sum < target) ++left;
                    else --right;
                }
            }
        }
        return vector<vector<int>>(res.begin(), res.end());
    }
};

但是毕竟用 TreeSet 来进行去重复的处理还是有些取巧,可能在 Java 中就不能这么做,那么还是来看一种比较正统的做法吧,手动进行去重复处理。主要可以进行的有三个地方,首先在两个 for 循环下可以各放一个,因为一旦当前的数字跟上面处理过的数字相同了,那么找下来肯定还是重复的。之后就是当 sum 等于 target 的时候了,在将四个数字加入结果 res 之后,left 和 right 都需要去重复处理,分别像各自的方面遍历即可,参见代码如下:

解法二:

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> res;
        int n = nums.size();
        sort(nums.begin(), nums.end());
        for (int i = 0; i < n - 3; ++i) {
            if (i > 0 && nums[i] == nums[i - 1]) continue;
            for (int j = i + 1; j < n - 2; ++j) {
                if (j > i + 1 && nums[j] == nums[j - 1]) continue;
                int left = j + 1, right = n - 1;
                while (left < right) {
                    long sum = (long)nums[i] + nums[j] + nums[left] + nums[right];
                    if (sum == target) {
                        vector<int> out{nums[i], nums[j], nums[left], nums[right]};
                        res.push_back(out);
                        while (left < right && nums[left] == nums[left + 1]) ++left;
                        while (left < right && nums[right] == nums[right - 1]) --right;
                        ++left; --right;
                    } else if (sum < target) ++left;
                    else --right;
                }
            }
        }
        return res;
    }
};

再来看一种更 generalize 的解法,为了避免 LeetCode 以后再出什么 5Sum 或 6Sum 的题目,我们还是来想一种 KSum 的解法吧,通用任意的数字。其实思路还是基于前面的经验,通过之前的 3Sum 和 4Sum 的题目,可以得出结论,都是要通过不断的固定数字,最后留出两个数字来进行双指针的遍历。对于 KSum 来说,就是要固定 K-2 的个数字,这里当然不能手动加 K-2 个 for 循环了,所以只能用递归来做,在 kSum 的函数中定义了一个递归函数 dfs,除了题目中给定的 nums 和 target 两个参数之外,还需要传入几个参数,分别是当前需要查找的个数k,查找范围左边界 left,右边界 right,以及当前数字组合 cur,和最终返回结果 res。

在递归函数中,判断若k等于2,说明此时可以使用双指针了,则进行 while 循环,若此时两个数字正好等于 target 了,则需要将两个数字都加入到 cur 中,然后将 cur 加入到结果 res 中。再将这两个数字从 cur 中移除,因为 cur 中本来可能有其他数字的,所以要移除新加的数字,以便尝试其他不同的组合。此时 left 和 right 分别跳过各自的重复数字,然后再分别移动一位即可。若两个数字之和小于 target,则左指针右移,否则右指针左移。若k大于2,则此时要固定数字,将 left 指向的数字加入到 cur 中,然后调用递归函数 dfs,此时的 target 要减去 nums[left](注意这里为了防止溢出,dfs 函数的 target 定义成了长整型),此时传入 k-1 和 left+1,之后恢复状态,将 nums[left] 从 cur 中移除。然后 left 指针跳过后面的重复数字,之后再向右移动一位即可,参见代码如下:

解法三:

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> res;
        int n = nums.size();
        sort(nums.begin(), nums.end());
        kSum(nums, target, 4, res);
        return res;
    }
    void kSum(vector<int>& nums, int target, int k, vector<vector<int>>& res) {
        vector<int> cur;
        dfs(nums, target, k, 0, (int)nums.size() - 1, cur, res);
    }
    void dfs(vector<int>& nums, long target, int k, int left, int right, vector<int>& cur, vector<vector<int>>& res) {
        if (k == 2) {
            while (left < right) {
                if (nums[left] + nums[right] == target) {
                    cur.push_back(nums[left]);
                    cur.push_back(nums[right]);
                    res.push_back(cur);
                    cur.pop_back();
                    cur.pop_back();
                    while (left < right && nums[left] == nums[left + 1]) ++left;
                    while (left < right && nums[right] == nums[right - 1]) --right;
                    ++left; --right;
                } else if (nums[left] + nums[right] < target) {
                    ++left;
                } else {
                    --right;
                }
            }
        }
        while (left < right) {
            cur.push_back(nums[left]);
            dfs(nums, target - nums[left], k - 1, left + 1, right, cur, res);
            cur.pop_back();
            while (left < right && nums[left] == nums[left + 1]) ++left;
            ++left;
        }
    }
};

Github 同步地址:

#18

类似题目:

Two Sum

3Sum

4Sum II

Count Special Quadruplets

参考资料:

https://leetcode.com/problems/4sum/

https://leetcode.com/problems/4sum/discuss/8549/My-16ms-c%2B%2B-code

https://leetcode.com/problems/4sum/discuss/8575/Clean-accepted-java-O(n3)-solution-based-on-3sum

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

@szh-maker
Copy link

两种方法会在求sum时溢出

@grandyang
Copy link
Owner Author

两种方法会在求sum时溢出

已修改,多谢指出~

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants