算法题-四数之和

题目链接:https://leetcode-cn.com/problems/4sum/
题目描述:
给定一个包含 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

类似于三数之和的问题,先对数组排序,在固定前两个数nums[i]和nums[j]的情况下,使用两个指针,来寻找另外两个数。
同时,在固定nums[i]时,通过当前可得到的最大值和最小值来和目标值进行比较,如果最小值都比目标值大,说明对所有的nums[i]都不可能会有满足条件的,直接结束i的循环;如果最大值比目标值小,说明对当前nums[i]不可能满足条件了,进入下一个循环。
同理,固定nums[j]时也是同样的处理。
接下来的处理,和三数之和相同。

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> ans = new ArrayList<>();
        int len = nums.length;
        if(nums == null ||len < 4) {
            return ans;
        }
        Arrays.sort(nums);
        for(int i=0;i<len-3;i++) {
            //去重,如果当前nums[i]和它前一个数相等,则跳过
            if(i>0 && nums[i] == nums[i-1]) continue;

            /*获取当前最小值,如果最小值比目标值大,说明后面越来越大的值不需要考虑,停止循环*/
            int min=nums[i]+nums[i+1]+nums[i+2]+nums[i+3];
            if(min>target){
                break;
            }
            /*获取当前最大值,如果最大值比目标值小,说明后面越来越小的值不需要考虑,进入下一个循环*/
            int max=nums[i]+nums[len-1]+nums[len-2]+nums[len-3];
            if(max<target){
                continue;
            }
 
            //第二个循环,固定第二个数
            for(int j=i+1;j<len-2;j++) {
                //去重
                if(j>i+1 && nums[j] == nums[j-1]) {
                    continue;
                }
                //两个指针
                int left = j+1;
                int right = len-1;
                /*获取当前最小值,如果最小值比目标值大,说明后面越来越大的值不许考虑,直接结束循环*/
                min=nums[i]+nums[j]+nums[j+1]+nums[j+2];
                if(min>target){
                    break;
                }
                /*获取当前最大值,如果最大值比目标值小,说明后面越来越小不需考虑,跳出此次循环*/
                max=nums[i]+nums[j]+nums[len-2]+nums[len-1];
                if(max<target){
                    continue;
                }

                //使用两个指针,left和right,left从j+1开始,right从len-1开始
                //当left<right时,
                //nums[i],nums[j],nums[left]和nums[right]的和为sum
                //如果sum>target,就将right前移,right--
                //如果sum<target,就将left后移,left++
                //sum等于target,先把这一组结果记录下来
                //将left++,right--
                //在left<right的前提下,如果nums[left]和它的前一个数nums[left-1]相等,就将left后移,left++
                //在left<right的前提下,如果nums[right]和它的后一个数nums[right+1]相等,就将right前移,right--
                while(left<right){
                    int sum = nums[i]+nums[j]+nums[left]+nums[right];
                    if(sum == target){
                        ans.add(Arrays.asList(nums[i],nums[j],nums[left],nums[right]));
                        left++;
                        right--;
                        while(left<right && nums[left] == nums[left-1]) {
                            left++;
                        }
                        while(left<right && nums[right] == nums[right+1]) {
                            right--;
                        }
                    }
                    else if(sum<target) {
                        left++;
                    }
                    else right--;
                }
            }
        }
        return ans;
    }
}
解答二

通过递归实现kSum。
将问题分解为: k等于2时,求两数之和;k>2时,求k-1个数之和。

class Solution {
    int len=0;
    public List<List<Integer>> fourSum(int[] nums, int target) {
        len=nums.length;
        //使用双指针数组先排序
        Arrays.sort(nums);
        //调用kSum
        return kSum(nums,target,4,0);
    }
    private List<List<Integer>> kSum(int[] nums,int target,int k,int index) {
        List<List<Integer>> res = new ArrayList<>();
        //index表示开始的下标
        if(index>=len) {
            return res;
        }
        //递归的出口,就是求两数之和
        //双指针求两数之和
        if(k == 2) {
            int left = index,right = len-1;
            while(left<right) {
                if(nums[left] + nums[right] == target) {
                    List<Integer> temp = new ArrayList<>();
                    temp.add(nums[left]);
                    temp.add(nums[right]);
                    res.add(temp);
                    left++;
                    right--;
                    while(left<right && nums[left] == nums[left-1]){
                        left++;
                    }
                    while(left<right && nums[right] == nums[right+1]){
                        right--;
                    }
                }
                else if(nums[left]+nums[right]>target) {
                    right--;
                } 
                else left++;
            }
        }
        else {
            for(int i=index;i<len-1;i++) {
                //当nums[i]重复时,直接跳过此次循环
                if(i>index && nums[i] ==nums[i-1]) continue;
                //当k>2时,递归调用kSum,通过递归和循环固定第一、二……的数,将target=target-第一、二……个数
                List<List<Integer>> temp = kSum(nums,target-nums[i],k-1,i+1);
                //当temp不为空时,说明找到和为target
                if(temp != null) {
                    //对于得到的temp中的每一个list,都将其对应nums[i]加入在0位置上
                    for(List<Integer> t :temp) {
                        t.add(0,nums[i]);
                        //将得到的每一个t加入res
                        res.add(t);
                    } 
                }                
            }
        }
        return res;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值