【LeetCode】18. 四数之和

一、题目

给定一个包含 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]
]

二、解决

1、暴力

思路: 四层循环,暴力求解,将答案放到Set中即可。
代码: 复杂度太高,必然超时,就不写了。
时间复杂度: O( n 4 n^4 n4)
空间复杂度: O(n)

2、三层循环+单数哈希

思路: 类似【LeetCode】15. 三数之和,有兴趣可看该文章。
代码:

class Solution {
    public List<List<Integer>> fourSum(int[] num, int target) {
        ArrayList<List<Integer>> ans = new ArrayList<>();
        if(num.length<4)return ans;
        Arrays.sort(num);
        for(int i=0; i<num.length-3; i++){
            if(num[i]+num[i+1]+num[i+2]+num[i+3]>target)break; //first candidate too large, search finished
            if(num[i]+num[num.length-1]+num[num.length-2]+num[num.length-3]<target)continue; //first candidate too small
            if(i>0&&num[i]==num[i-1])continue; //prevents duplicate result in ans list
            for(int j=i+1; j<num.length-2; j++){
                if(num[i]+num[j]+num[j+1]+num[j+2]>target)break; //second candidate too large
                if(num[i]+num[j]+num[num.length-1]+num[num.length-2]<target)continue; //second candidate too small
                if(j>i+1&&num[j]==num[j-1])continue; //prevents duplicate results in ans list
                int low=j+1, high=num.length-1;
                while(low<high){
                    int sum=num[i]+num[j]+num[low]+num[high];
                    if(sum==target){
                        ans.add(Arrays.asList(num[i], num[j], num[low], num[high]));
                        while(low<high&&num[low]==num[low+1])low++; //skipping over duplicate on low
                        while(low<high&&num[high]==num[high-1])high--; //skipping over duplicate on high
                        low++; 
                        high--;
                    }
                    //move window
                    else if(sum<target)low++; 
                    else high--;
                }
            }
        }
        return ans;
    }
}

时间复杂度: O( n 3 n^3 n3)
空间复杂度: O( n n n)

3、两层循环+两数和哈希

思路: 类似2.
代码:

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {

        if(nums == null || nums.length < 4) {
            return new ArrayList<>(res);
        }
        int n = nums.length;
        Arrays.sort(nums);
        //key represents sum of first two nums, value is the list of indexes of those two nums
        //We dont need to worry about duplicate here, we deal with that in res set
        Map<Integer, List<int[]>> map = new HashMap<>();
        //because res can be duplicated, we use set to deal with that
        //Set of a list compare all the elements in the list defaultly, you don't need to override compare and hash function
        Set<List<Integer>> res = new HashSet<List<Integer>>();
        for(int i = 0; i < n; i++) {
            for(int j = i + 1; j < n; j++) {
                int sum = nums[i] + nums[j];
                //check if current nums[i] and nums[j] can combine with previous first two nums to get a target value; 
				if(map.containsKey(target - sum)) {
                    List<int[]> indexes = map.get(target - sum);
					for(int[] index : indexes) {
                        // Here we already know that we can get 4 nums to get the target value, but we need those four nums comes in order
						//assume we have first two nums nums[k], nums[l], and current nums are nums[i], nums[j], by definition, k < l and i < j are certain. So we only need to make sure that  l < i, then we can get k < l < i < j, and add it to result set 
						if(index[1] < i) {
                            //make a result
                            // asList(),初始化,静态,不支持增删元素
                            List<Integer> candidate = Arrays.asList(nums[index[0]], nums[index[1]], nums[i], nums[j]);
                            res.add(candidate);
                        }
                    }
                }
                List<int[]> temp = map.getOrDefault(sum, new ArrayList<>());
                temp.add(new int[]{i, j});
                map.put(sum, temp);
            }
        }
        //convert from Set<List> to List<List>
        return new ArrayList<>(res);
    }
}

时间复杂度: O( n 2 n^2 n2)
空间复杂度: O( n n n)

4、回溯

思路: 暂时不太理解,后续懂了再补充吧。
代码:

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> ans = new ArrayList<List<Integer>>();
        if(nums == null || nums.length == 0) return ans;
        List<Integer> list = new ArrayList<>();
        Arrays.sort(nums);
        getSum(nums, 0, target, ans, list, 0);
        return ans;
    }
    private void getSum(int[] nums, int sum, int target, List<List<Integer>> ans, List<Integer> list, int pos){
        if(list.size() == 4 && sum == target && !ans.contains(list)){
            ans.add(new ArrayList<>(list)); return;
        }else if(list.size() == 4) return;
        for(int i = pos; i < nums.length; i++){
            if(nums[i] +  nums[nums.length - 1] * (3 - list.size()) + sum < target) continue;
            if(nums[i] * (4 - list.size()) + sum > target) return;
            list.add(nums[i]);
            getSum(nums, sum + nums[i], target, ans, list, i + 1);
            list.remove(list.size() - 1);
        }
    }
}

时间复杂度: 略。
空间复杂度: 略。

5、递归(K数相加)

思路: 注意整数极限。
代码:

class Solution {

    public List<List<Integer>> fourSum(int[] nums, int target) {
        Arrays.sort(nums);
        return kSum(nums, 0, 4, target);
    }
    private List<List<Integer>> kSum (int[] nums, int start, int k, int target) {
        int len = nums.length;
        List<List<Integer>> res = new ArrayList<List<Integer>>();
        if(k == 2) { //two pointers from low and high
            int low = start, high = len - 1;
            while(low < high) {
                int sum = nums[low] + nums[high];
                if(sum == target) {
                    List<Integer> path = new ArrayList<Integer>();
                    path.add(nums[low]);
                    path.add(nums[high]);
                    res.add(path);
                    while(low < high && nums[low] == nums[low + 1]) low++;
                    while(low < high && nums[high] == nums[high - 1]) high--;
                    low++;
                    high--;
                } else if (sum < target) { //move low
                    low++;
                } else { //move high
                    high--;
                }
            }
        } else {
            for(int i = start; i < len - (k - 1); i++) {
                if (i > start && (long)nums[i]*k > target) break;
                if (i > start &&  (long)nums[i] + (long)nums[nums.length - 1] *(k-1) < target) continue;
                if(i > start && nums[i] == nums[i - 1])  continue;
                // use current number to reduce kSum into k-1 sum
                List<List<Integer>> resTemp = kSum(nums, i + 1, k - 1, target - nums[i]);
                for(List<Integer> t : resTemp) {
                   t.add(0, nums[i]);
                }
                res.addAll(resTemp);
            }
        }
        return res;
    }
}

时间复杂度: O( n k − 1 n^{k-1} nk1)
空间复杂度: O( k n 2 kn^2 kn2)

三、参考

1、(Java) 25 lines of code, easy to understand answer that is O(n^2), based on two sum solution
2、Clean accepted java O(n^3) solution based on 3sum
3、3, 4,N数之和解法 双指针 通俗易懂
4、20-line, elegant accepted JAVA solution using Backtracking.
5、My solution generalized for kSums in JAVA
6、K数之和的通用模板
7、2sum->3sum->4sum->…->nsum
8、List add(int index, E element) method in Java
9、在 Java 中初始化 List 的五种方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值