Loading

四数之和

1.问题描述

给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。

注意:

答案中不可以包含重复的四元组。

示例:

给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。

满足要求的四元组集合为:
[
  [-1,  0, 0, 1],
  [-2, -1, 1, 2],
  [-2,  0, 0, 2]
]

2.求解

双指针法

  1. 为了去除所有重复,我们第一步需要做的是先对数组进行排序
  2. 两层for循环嵌套固定a,b,令b的初始值为a + 1
  3. 固定两个指针x,y,令x的初始值为b+1,y的初始值为nums.length - 1
  4. 比较target与a,b,c,d的和sum,若sum > target,则令d指针前移,若sum < target,则令c指针后移
  5. 特别注意的一点是对相同元素的处理,对于a,b,c,d的值变化时,每次都需要判断是否等于上一个值,若相等,则跳过
代码如下
    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> list = new ArrayList<>();
        if (nums == null || nums.length < 4) {
            return list;
        }
        int len = nums.length;
        Arrays.sort(nums);
        for (int i = 0; i < len - 3; i++) {
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            for (int j = i + 1; j < len - 2; j++) {
                if (j > i + 1 && nums[j] == nums[j - 1]) {
                    continue;
                }
                int x = j + 1;
                int y = len - 1;
                while (x < y) {
                    int a = nums[i];
                    int b = nums[j];
                    int c = nums[x];
                    int d = nums[y];
                    int sum = a + b + c + d;
                    if (sum > target) {
                        //跳过c重复的值
                        while (x < y && nums[y] == nums[--y]) ;
                    } else if (sum < target) {
                        //跳过d重复的值
                        while (x < y && nums[x] == nums[++x]) ;
                    } else {
                        list.add(Arrays.asList(a, b, c, d));
                        while (x < y && nums[y] == nums[--y]) ;
                        while (x < y && nums[x] == nums[++x]) ;
                    }
                }
            }
		}
        return list;
    }
  • 时间复杂度O(n³),双指针枚举剩下的两个数的时间复杂度是 O(n),加上双重循环的O(n²)
  • 空间复杂度,O(log n),其中 n是数组的长度。空间复杂度主要取决于排序额外使用的空间。

一开始写的时候犯了个错误,判断空直接判断长度了,应该写判断对象引用是否为null,再判断长度,因为null值是没有length属性的。

ps:写完后对比大佬们的解法,发现还可以对一些情况进行剪枝操作

  1. 在确定第一个数后,如果第一个数加后续三个数的值大于target,则跳过
  2. 在确定第一个数后,如果第一个数加倒数三个数的值小于target,则跳过
  3. 在确定第二个数后,如果第一、二个数加后续两个数的值大于target,则跳过
  4. 在确定第二个数后,如果第一、二个数加倒数两个数的值小于于target,则跳过

回溯解法

看到解答区有位大佬写了个回溯解法,学习了一下,回溯不太好理解,感觉难以理解可以一步步推下去,相比而言剪枝操作就很好理解了。

  1. 剩余元素少于不确定的数字个数,直接返回,当前深入终止
  2. 前后两个元素相等,继续下一步循环
  3. 顺序几个数相加大于target(已经排序完成),直接返回,当前深入无解
  4. 当前数加倒数数小于target,继续下一个循环
public class Solution {

    List<List<Integer>> ans = new ArrayList<>();
    List<Integer> list = new ArrayList<>();
    int cur = 0;

    public List<List<Integer>> fourSum(int[] nums, int target) {
        Arrays.sort(nums);
        dfs(nums, target, 0);
        return ans;
    }

    void dfs(int[] nums, int target, int begin) {
        if (list.size() == 4) {
            if (cur == target) {
                ans.add(new ArrayList<>(list));
            }
            return;
        }
        for (int i = begin; i < nums.length; i++) {
            if (nums.length - i < 4 - list.size()) {
                return;
            }
            if (i > begin && nums[i] == nums[i++]) {
                continue;
            }
            if (i < nums.length - 1 && cur + nums[i] + (3 - list.size()) * nums[i + 1] > target) {
                return;
            }
            if (i < nums.length - 1 && cur + nums[i] + (3 - list.size()) * nums[nums.length - 1] < target) {
                continue;
            }
            cur += nums[i];
            list.add(nums[i]);
            dfs(nums, target, i + 1);
            list.remove(list.size() - 1);
            cur -= nums[i];
        }
    }
}
posted @ 2020-10-05 20:17  水纸杯  阅读(172)  评论(0)    收藏  举报