哈希表_18.四数之和

该博客介绍了如何使用排序和双指针优化算法来解决寻找数组中四个元素的和为目标值的问题。通过排序数组,然后用两重循环配合双指针在O(n^3)的时间复杂度内找到所有不重复的四元组。同时,文章还提到了一些剪枝技巧以提高效率。
摘要由CSDN通过智能技术生成
给定一个包含 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]
]
方法:排序 + 双指针
思路与算法:

	最朴素的方法是使用四重循环枚举所有的四元组,然后使用哈希表进行去重操作,得到不包含重复四元组的最终答案。
假设数组的长度是 nnn,则该方法中,枚举的时间复杂度为O(n^4),去重操作的时间复杂度和空间复杂度也很高,因此需要换一种思路。

	为了避免枚举到重复四元组,则需要保证每一重循环枚举到的元素不小于其上一重循环枚举到的元素,
且在同一重循环中不能多次枚举到相同的元素。

	为了实现上述要求,可以对数组进行排序,并且在循环过程中遵循以下两点:
    	每一种循环枚举到的下标必须大于上一重循环枚举到的下标;
    	同一重循环中,如果当前元素与上一个元素相同,则跳过当前元素。

	使用上述方法,可以避免枚举到重复四元组,但是由于仍使用四重循环,时间复杂度仍是O(n^4)。
注意到数组已经被排序,因此可以使用双指针的方法去掉一重循环。

	使用两重循环分别枚举前两个数,然后在两重循环枚举到的数之后使用双指针枚举剩下的两个数。
假设两重循环枚举到的前两个数分别位于下标 i 和 j,其中 i<j。
初始时,左右指针分别指向下标 j+1 和下标 n−1。每次计算四个数的和,并进行如下操作:

    如果和等于target,则将枚举到的四个数加到答案中,然后将左指针右移直到遇到不同的数,将右指针左移直到遇到不同的数;
    如果和小于target,则将左指针右移一位;
    如果和大于target,则将右指针左移一位。
使用双指针枚举剩下的两个数的时间复杂度是 O(n),因此总时间复杂度是O(n^3),低于O(n^4)。

	具体实现时,还可以进行一些剪枝操作:
    在确定第一个数之后,如果 nums[i]+nums[i+1]+nums[i+2]+nums[i+3]>target,说明此时剩下的三个数无论取什么值,四数之和一定大于target,因此退出第一重循环;
    在确定第一个数之后,如果 nums[i]+nums[n−3]+nums[n−2]+nums[n−1]<target,说明此时剩下的三个数无论取什么值,四数之和一定小于 target,因此第一重循环直接进入下一轮,枚举 nums[i+1];
    在确定前两个数之后,如果 nums[i]+nums[j]+nums[j+1]+nums[j+2]>target,说明此时剩下的两个数无论取什么值,四数之和一定大于 target,因此退出第二重循环;
    在确定前两个数之后,如果 nums[i]+nums[j]+nums[n−2]+nums[n−1]<target,说明此时剩下的两个数无论取什么值,四数之和一定小于 target,因此第二重循环直接进入下一轮,枚举 nums[j+1]
class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        Arrays.sort(nums);
        HashSet<List<Integer>> hashSet=new HashSet<>();
        int length=nums.length;
        for(int start=0;start<length-3;start++){
        	//同一重循环中,如果当前元素与上一个元素相同,则跳过当前元素。
            if(start>0&&nums[start]==nums[start-1]){
                continue;
            }
            //数组中最小的数加起来都大于target,说明没有其他数等于target了,注意break
            if(nums[start]+nums[start+1]+nums[start+2]+nums[start+3]>target){
                break;
            }
            //当start固定时,数组中最大的数加起来都小于target,说明当前start位置没有其他数等于target了
            if(nums[start]+nums[length-1]+nums[length-2]+nums[length-3]<target){
                continue;
            }
            for(int end=start+1;end<length-2;end++) {
            	//同一重循环中,如果当前元素与上一个元素相同,则跳过当前元素。
                if(end>start+1&&nums[end]==nums[end-1]){
                    continue;
                }
                //为什么需要这一步eg:[-3,-2,-1,0,1,2] -6 前4个加起来=-6,所以上一个break没起作用,当end=2时,数组中最小的相加都大于target,接下来就不用运行了
                if(nums[start]+nums[end]+nums[end+1]+nums[end+2]>target){
                    break;
                }
                //当start和end固定时,数组中最大的数加起来都小于target,说明当前start和end位置没有其他数等于target了。end向右走,整体是增大的
                if(nums[start]+nums[end]+nums[length-1]+nums[length-2]<target){
                    continue;
                }
                int left = end + 1;
                int rigth = length - 1;
                while (left < rigth) {
                    int sum = nums[start] + nums[end] + nums[left] + nums[rigth];
                    List<Integer> list = new ArrayList<>();
                    if (sum == target) {
                        list.add(nums[start]);
                        list.add(nums[end]);
                        list.add(nums[left]);
                        list.add(nums[rigth]);
                        hashSet.add(list);
                        while(left<rigth&&nums[left]==nums[left+1]){
                            left++;
                        }
                        left++;
                        while(left<rigth&&nums[rigth]==nums[rigth-1]){
                            rigth--;
                        }
                        rigth--;
                    } else if (sum > target) {
                        rigth--;
                    } else if (sum < target) {
                        left++;
                    }
                }
            }
        }
        List<List<Integer>> result=new ArrayList<>();
        for(List<Integer> li : hashSet){
            result.add(li);
        }
        return result;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值