LeetCode 18. 4Sum

题目

Given an array nums of n integers and an integer target, are there elements abc, and d in nums 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.

Example:

Given array nums = [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]
]

4sum好像没有别的好方法了,只能是在3sum外面多套一层循环,先固定第一个,然后后面用3sum的方法做。直接复制粘贴了3sum的代码,外面套了层循环,注意这里是找target,target不一定是0,所以3sum的遇到正数就停的if要删掉(3sum是求和为0)。时间复杂度O(n^3),空间看排序。

Runtime: 9 ms, faster than 87.75% of Java online submissions for 4Sum.

Memory Usage: 40.2 MB, less than 53.62% of Java online submissions for 4Sum.

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        Arrays.sort(nums);
        List<List<Integer>> result = new ArrayList<>();
        for (int i = 0; i < nums.length - 3; i++) {
            // duplicate elimination
            if (i != 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            int newTarget = target - nums[i];
            for (int j = i + 1; j < nums.length - 2; j++) {
                // duplicate elimination
                if (j != i + 1 && nums[j] == nums[j - 1]) {
                    continue;
                }
                // use two pointers to find pairs
                int pStart = j + 1;
                int pEnd = nums.length - 1;
                int remain = newTarget - nums[j];

                while (pStart < pEnd) {
                    if (nums[pStart] + nums[pEnd] > remain) {
                        pEnd--;  // don't need to skip duplicate
                    }
                    else if (nums[pStart] + nums[pEnd] < remain) {
                        pStart++;  // don't need to skip duplicate
                    }
                    else {
                        List<Integer> pair = new ArrayList<>();
                        pair.add(nums[i]);
                        pair.add(nums[j]);
                        pair.add(nums[pStart]);
                        pair.add(nums[pEnd]);
                        result.add(pair);
                        do {
                            pStart++;
                        } while (nums[pStart - 1] == nums[pStart] && pStart < pEnd);  // remember pStart < pEnd
                        do {
                            pEnd--;
                        } while (nums[pEnd + 1] == nums[pEnd] && pStart < pEnd);  // remember pStart < pEnd
                    }
                }
            }
        }
        return result;
    }
}

discussion里还有看到可以在2sum外层循环中先根据前两个和后两个元素相加判断是否能够满足要求的,如果前两个(最小的两个)加起来比target大或者后两个(最大的两个)加起来比target小,那么现在这个fix元素就不可能存在符合要求的解。

ksum问题还可以generalize,暂时不写了,见:https://leetcode.com/problems/4sum/discuss/8609/My-solution-generalized-for-kSums-in-JAVA【算法】2SUM/3SUM/4SUM问题-CSDN博客


2024.6.27 再次重写,结果前两天写的3sum的去重这次就忘了……漏了在sum == target时high--和low++的时候还需要while loop去重一下。以及好像lc加了新的test case,会出现integer overflow的情况,比如这个case:

Input
nums =
[1000000000,1000000000,1000000000,1000000000]
target =
-294967296
Output
[]

这个需要特殊处理在求sum的时候,结果值要用long,且在计算的时候也要explicitly cast to long:long sum = (long) nums[i] + nums[j] + ...

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        Arrays.sort(nums);
        List<List<Integer>> result = new ArrayList<>();
        for (int i = 0; i < nums.length; i++) {
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            int remain = target - nums[i];
            for (int j = i + 1; j < nums.length; j++) {
                if (j > i + 1 && nums[j] == nums[j - 1]) {
                    continue;
                }
                int low = j + 1;
                int high = nums.length - 1;
                while (low < high) {
                    long sum = (long) nums[j] + nums[low] + nums[high];
                    if (sum == remain) {
                        result.add(List.of(nums[i], nums[j], nums[low], nums[high]));
                        while (low < high && nums[low + 1] == nums[low]) {
                            low++;
                        }
                        while (low < high && nums[high - 1] == nums[high]) {
                            high--;
                        }
                        low++;
                        high--;
                    } else if (sum < remain) {
                        low++;
                    } else {
                        high--;
                    }
                }
            }
        }
        return result;
    }
}

以下是k sum。

其实用一个递归降维成k - 1 sum,最终会变成2 sum的。对于递归其实还是写的不熟,刚开始不太知道要怎么去设置递归函数的参数。肯定需要加的就是k,因为每次要k -1。另外一点就是需要记录我们这次从第几个开始开始找的,就死要把fixed的放在外面,所以这个index也需要作为参数。递归的返回值是这个list of list的result。

在递归函数里面,递归的终止条件就是当k == 2的时候,照搬two pointers的方法就好了。当k > 2的时候可以讨论一下,其实本质就还是那个for loop,但是注意这里for loop的起始点是index而不是0!这里写错被卡了好久……也是一样的去重方法,然后for loop里recursively call kSum with k - 1和index + 1。刚开始总想着我怎么把这个nums[i]传进去让recursive call直接拿结果,其实可以call完了回来再处理。然后,这里又来了一个大坑,关于java语法的。因为一直习惯性用List.of()来生成已知值的list,但是不知道List.of() return的是一个structurally immutable的list。于是在这里拿到tempResult想往里加nums[i]的时候就被throw了java.lang.UnsupportedOperationException about java.base/java.util.ImmutableCollections$AbstractImmutableList.add。查了一下,Arrays.asList和List.of都是kind of like this,Arrays.asList返回的是一个fixed size list所以不能往里加东西,而List.of更夸张了直接是structurally immutable。感觉我的理解还不是很深。这个是参考的帖子:list - UnsupportedOperationException at java.util.AbstractList.add - Stack Overflowjava - What is the difference between List.of and Arrays.asList? - Stack Overflow 最后的解决方案就是在后面add的时候重新make a new copy来加。

然后还是遇到了integer overflow的问题,最后通过把递归函数参数里的target改成long解决。这样的话里面求sum也不需要explicitly cast了。

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        Arrays.sort(nums);
        return kSum(nums, target, 4, 0);
    }

    private List<List<Integer>> kSum(int[] nums, long target, int k, int index) {
        List<List<Integer>> result = new ArrayList<>();
        if (index >= nums.length) {
            return result;
        }

        if (k == 2) {
            int low = index;
            int high = nums.length - 1;
            while (low < high) {
                int sum = nums[low] + nums[high];  // no need to use long to pass the test case
                if (sum == target) {
                    result.add(Arrays.asList(nums[low], nums[high]));
                    while (low < high && nums[low + 1] == nums[low]) {
                        low++;
                    }
                    while (low < high && nums[high - 1] == nums[high]) {
                        high--;
                    }
                    low++;
                    high--;
                } else if (sum < target) {
                    low++;
                } else {
                    high--;
                }
            }
        } else {
            for (int i = index; i < nums.length - k + 1; i++) {
                if (i > index && nums[i] == nums[i - 1]) {
                    continue;
                }
                // store the result in a temp list and then add the initial number
                List<List<Integer>> tempResult = kSum(nums, target - nums[i], k - 1, i + 1);
                for (List<Integer> list : tempResult) {
                    List<Integer> temp = new ArrayList<>(list);
                    temp.add(nums[i]);
                    result.add(temp);
                }
            }

        }
        return result;

    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值