018 4Sum[M]

1 题目描述

Given an array
S of
n integers, are there elements
a,
b,
c, and
d in
S such that
a +
b +
c +
d = target? Find all unique quadruplets in the array which gives the sum of target.

Note: The solution set must not contain duplicate quadruplets.

难度:Medium

2 题目样例

For example, given array S = [1, 0, -1, 0, -2, 2], and target = 0.

A solution set is:
[
  [-1,  0, 0, 1],
  [-2, -1, 1, 2],
  [-2,  0, 0, 2]
]

3 题意分析

给定数组,寻找所有和为target的组合,也就是说[1,2,3,4]和[4,2,3,1]是同一个组合不应该在答案中出现两次。

4 思路分析

1 暴力枚举

先对数组排序,接着假设 《018 4Sum[M]》 ,然后分别遍历 《018 4Sum[M]》 ,在剩下的数组中二分查找 《018 4Sum[M]》 ,同时利用std::set来判重。

(似乎直接四重循环更加暴力?)

代码如下

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        int len = nums.size();
        set<vector<int>> s;
        vector<vector<int>> ans;
        sort(nums.begin(), nums.end());
        for(int i=0;i<len-3;i++)
            for(int j=i+1;j<len-2;j++)
                for(int k=j+1;k<len-1;k++)
                    if(binary_search(&nums[k+1], &nums[len], target - nums[i] - nums[j] - nums[k]))
                        s.insert({nums[i], nums[j], nums[k], target - nums[i] - nums[j] - nums[k]});
        for(auto &v:s)
            ans.emplace_back(v);
        return ans;
    }
};

排序的复杂度为 《018 4Sum[M]》 ,std::set::insert()的复杂度是 《018 4Sum[M]》 ,所以时间复杂度是 《018 4Sum[M]》 ,空间复杂度不太好分析,但应该有 《018 4Sum[M]》 ?此处存疑。

我觉得我仿佛找到了Nsum的通解!(逃

2 优化查找

还记得在3Sum的解法吗?

没错我们依旧可以参考3Sum的做法,先对数组排序,接着假设 《018 4Sum[M]》 ,但是只遍历 《018 4Sum[M]》 ,然后在剩下的数组空间里寻找所有满足条件的 《018 4Sum[M]》《018 4Sum[M]》

具体说来就是,假设 《018 4Sum[M]》《018 4Sum[M]》 ,我们不断移动 《018 4Sum[M]》《018 4Sum[M]》 ,然后令 《018 4Sum[M]》 ,在保证的 《018 4Sum[M]》 的情况下作如下循环

  • 如果 《018 4Sum[M]》 ,说明这时满足题意,把 《018 4Sum[M]》 加入答案,同时 《018 4Sum[M]》《018 4Sum[M]》 分别向右和向左移动到非重复元素为止。
  • 如果 《018 4Sum[M]》 说明 《018 4Sum[M]》 大了,需要把 《018 4Sum[M]》 向左移动。
  • 如果 《018 4Sum[M]》 说明 《018 4Sum[M]》 小了,需要把 《018 4Sum[M]》 向右移动。

具体代码如下

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        int len = nums.size();
        sort(nums.begin(),nums.end());
        vector<vector<int>> ans;
        int tp;
        int l;
        int r;
        for(int i=0;i<len-3;i++){
            while(i<len-3&&i>0&&nums[i]==nums[i-1])
                i++;
            for(int j=i+1;j<len-2;j++){
                while(j<len-2&&j>i+1&&nums[j]==nums[j-1])
                    j++;
                l = j + 1;
                r = len -1;
                while(l<r){
                    tp = nums[i] + nums[j] + nums[l] + nums[r] - target;
                    if(tp==0){
                        ans.push_back({nums[i], nums[j], nums[l], nums[r]});
                        while(l<r&&nums[l]==nums[l+1])
                            l++;
                        l++;
                        while(l<r&&nums[r]==nums[r-1])
                            r--;
                        r--;
                    }
                    else if(tp > 0)
                        r--;
                    else
                        l++;
                }
            }
        }
        return ans;
    }
};

这里有一个细节就是在每次循环对i和j的处理,保证了相同的元素只会被处理一次,防止出现重复组合。

整体时间复杂度 《018 4Sum[M]》 ,空间复杂度依旧存疑。

3 进一步优化

刚才思路2中的代码实际上还有很多情况可以优化,比如

  • 如果 《018 4Sum[M]》 ,那么显然应该直接跳出。
  • 如果 《018 4Sum[M]》 ,那么显然应该直接遍历下一个元素。

实际上这些优化在3Sum的过程中也可以完成的,这里我直接粘讨论区的代码了

public List<List<Integer>> fourSum(int[] nums, int target) {
		ArrayList<List<Integer>> res = new ArrayList<List<Integer>>();
		int len = nums.length;
		if (nums == null || len < 4)
			return res;

		Arrays.sort(nums);

		int max = nums[len - 1];
		if (4 * nums[0] > target || 4 * max < target)
			return res;

		int i, z;
		for (i = 0; i < len; i++) {
			z = nums[i];
			if (i > 0 && z == nums[i - 1])// avoid duplicate 				continue;
			if (z + 3 * max < target) // z is too small 				continue;
			if (4 * z > target) // z is too large 				break;
			if (4 * z == target) { // z is the boundary 				if (i + 3 < len && nums[i + 3] == z)
					res.add(Arrays.asList(z, z, z, z));
				break;
			}

			threeSumForFourSum(nums, target - z, i + 1, len - 1, res, z);
		}

		return res;
	}

	/*  * Find all possible distinguished three numbers adding up to the target  * in sorted array nums[] between indices low and high. If there are,  * add all of them into the ArrayList fourSumList, using  * fourSumList.add(Arrays.asList(z1, the three numbers))  */
	public void threeSumForFourSum(int[] nums, int target, int low, int high, ArrayList<List<Integer>> fourSumList,
			int z1) {
		if (low + 1 >= high)
			return;

		int max = nums[high];
		if (3 * nums[low] > target || 3 * max < target)
			return;

		int i, z;
		for (i = low; i < high - 1; i++) {
			z = nums[i];
			if (i > low && z == nums[i - 1]) // avoid duplicate 				continue;
			if (z + 2 * max < target) // z is too small 				continue;

			if (3 * z > target) // z is too large 				break;

			if (3 * z == target) { // z is the boundary 				if (i + 1 < high && nums[i + 2] == z)
					fourSumList.add(Arrays.asList(z1, z, z, z));
				break;
			}

			twoSumForFourSum(nums, target - z, i + 1, high, fourSumList, z1, z);
		}

	}

	/*  * Find all possible distinguished two numbers adding up to the target  * in sorted array nums[] between indices low and high. If there are,  * add all of them into the ArrayList fourSumList, using  * fourSumList.add(Arrays.asList(z1, z2, the two numbers))  */
	public void twoSumForFourSum(int[] nums, int target, int low, int high, ArrayList<List<Integer>> fourSumList,
			int z1, int z2) {

		if (low >= high)
			return;

		if (2 * nums[low] > target || 2 * nums[high] < target)
			return;

		int i = low, j = high, sum, x;
		while (i < j) {
			sum = nums[i] + nums[j];
			if (sum == target) {
				fourSumList.add(Arrays.asList(z1, z2, nums[i], nums[j]));

				x = nums[i];
				while (++i < j && x == nums[i]) // avoid duplicate 					;
				x = nums[j];
				while (i < --j && x == nums[j]) // avoid duplicate 					;
			}
			if (sum < target)
				i++;
			if (sum > target)
				j--;
		}
		return;
	}

复杂度相对思路2来说没有变化,但是这些剪枝有效降低了代码运行时间

5 后记

从2Sum到NSum,的确是一系列相当不错的题呢。

不知道对于NSum有没有更好的优化方法,现在主要还是依赖查询线性优化来让NSum的时间复杂度降低到 《018 4Sum[M]》

    原文作者:澪同学
    原文地址: https://zhuanlan.zhihu.com/p/33773991
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞