两数,三数,四数之和 ---- 解法及优化

1 两数之和

两数之和 ---- 题目链接
要求:
给定一个整数组nums和一个目标值target,请你在该数组中找出和为目标值的那两个整数,
并返回他们的数组下标。
假设每种输入只会对应一个答案,但是不能重复利用这个数组中同样的元素

1.1 双层for(枚举):

枚举在数据中所有的不同的两个下标的组合
逐个检查他们所对应的数的和是否等于target

  • 时间复杂度:O(n²),这里n为数组长度
  • 空间复杂度:O(1),只用到常数个临时变量
public int[] twoSum(int[] nums,int target){
        int[] array=new int[2];//也可以不定义返回匿名数组
        for (int i = 0; i <nums.length ; i++) {
            for(int j=i+1;j<nums.length-i;j++){
                if (nums[i]+nums[j]==target){
                    array[0]=i;
                    array[1]=j;
                }
            }
        }
        /*[0]+[1]=2+5   [0]+[2]=2+7=9     [0]+[3]=2+6
          [1]+[2]=5+7   [1]+[3]=5+6
          [2]+[3]=7+6
        *
        * */
        return array;
    }

1.1.2 返回匿名数组,但要创建抛出异常对象

public static int[] twoSums(int[] m,int n){
        for (int i = 0; i < m.length; i++) {
            for (int j=i+1;j<m.length;j++){
                if (m[i]+m[j]==n){
                    return new int[]{i,j};
                }
            }
        }
        throw new IllegalArgumentException("未找到");
    }

在这里插入图片描述

测试

public static void main(String[] args) {
        int[] a={2,5,7,6};
        int[] arr=twoSums(a,9);
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }

1.2 HashMap

空间换取时间,少一层for循环
查表
空间复杂度:O(n),哈希表中最多需要存n-1个键值对
循环查询值,若不存在map.get( )==null;则存入map中

    public int[] twoSum(int[] nums, int target) {
        //hashmap 初始化时,尽量指定初始值大小,虽然hashmap是可以自动扩容的,但是不指定会频繁消耗资源
        int len=nums.length;
        HashMap<Integer, Integer> map = new HashMap<>(len-1);
        int[] res=new int[2];
        for (int i = 0; i < len; i++) {//i=0;          i=1;            i=2;
            int dif=target-nums[i];                 //dif=7;        dif=4;          dif=2;
            if (map.get(dif)!=null){        //=null;        =null;          !=null;   map.get(key)=0
                res[0]=map.get(dif);        //                              res[0]=0;
                res[1]=i;                   //                              res[1]=2;
                return res;                 //                              res={0,2};结束
            }
            map.put(nums[i],i);                //put(2,0);     put(5,1)
        }
        return res;
    }

在这里插入图片描述

2 三数之和

三数之和 ---- 题目链接
要求
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?
请你找出所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]

示例2:
输入:nums = [ ]
输出:[ ]

示例3:
输入:nums = [0]
输出:[]

2.1 排序+Set+双指针

1.先对数组进行排序,升序,Arrays.sort( )
2.先固定一个数比如数组中第一个数nums[0],然后再用两个数分别指向后面剩余的两端,
L=i+1;
R=n-1; (n是数组长度)
3.循环条件while(L<R)
1)若和=0,则把他们放入Set集合中 (Set去重),缩小范围L++ R–
2)如果当前三个所指向数组中的数的和<0,则L++;
(因为数组是先按照从小到大排序的,R是最大的,那么前面两个数和R之前的数相加也是<0的,所以L++;)
3)反之,若和>0,R- -

在这里插入图片描述

public static List<List<Integer>> threeS2(int[] nums){
        Arrays.sort(nums);//排序
        int n=nums.length;
        Set<List<Integer>> res = new HashSet<>();//利用Set去重

        for (int i = 0; i < n; i++) {
        //定义两个数分别指向i+1和数组末端
            int l=i+1;
            int r=n-1;
            while (l<r){
                if (nums[i]+nums[l]+nums[r]==0){
                    res.add(Arrays.asList(nums[i],nums[l],nums[r]));//若符合则添加到res
                    l++;
                    r--;
                }else if (nums[i]+nums[l]+nums[r]<0){
                    l++;
                }else{
                    r--;
                }
            }
        }
        ArrayList<List<Integer>> ans = new ArrayList<>();
        ans.addAll(res);
        return ans;
    }

虽然利用set去重简化了代码量,但最后set要放入list ,较耗时

public static void main(String[] args) {
        int[] a={-1,0,1,2,-1,-4};
        List<List<Integer>> an=threeS2(a);
        for (int i = 0; i <an.size() ; i++) {
            System.out.println(an.get(i));
        }
    }

在这里插入图片描述
在这里插入图片描述

代码运行解析

index i012345actiongoto
-4-1-1012
i=0L=1R=5-4+(-1)+2<0L++
i=0L=2R=5-4+(-1)+2<0L++
i=0L=3R=5-4+0+2<0L++
i=0L=4R=5-4+1+2<0L++
i=0L=5=RL<R=falsei++
i=1L=2R=5-1+(-1)+2=0L++,R- -
i=1L=3R=4-1+0+1=0L++,R- -
i=1R=3L=4L<R=falsei++
i=2L=3R=5-1+0+2>0R- -
i=2L=3R=4-1+0+1=0L++,R- -
i=2R=3L=4L<R=falsei++
i=3L=4R=50+1+2>0R- -
i=3L=4=RL<R=falsei++
i=4L=5,R=5L<R=falsei++
i=5,R=5,L=6L<R=falseend

一些方法说明:

1.Arrays.asList()方法

asList方法接收一个泛型化的可变参数列表
API中的说明
在这里插入图片描述

2.2 排序+枚举+双指针

注意去重

    public List<List<Integer>> threeSum1_1(int[] nums) {
        int len=nums.length;
        List<List<Integer>> res=new ArrayList<>(len);
        Arrays.sort(nums);
        //测试集合长度小于3 或 排序后 最大值<0 直接返回空集合
        if(len<3||nums[len-1]<0){
            return res;
        }
        for (int i = 0; i < len-2; i++) {
            if(nums[i]>0){
                break; // 最小值大于0直接跳出
            }
            //满足边界条件 且 当前下标值与前一个值相同,跳出进行下一轮循环
            if (i>0&&nums[i]==nums[i-1]){//去重
                continue;
            }
            int l=i+1;
            int r=len-1;
            while (l<r) {
                int sum=nums[i] + nums[l] + nums[r];
                if (sum == 0) {
                    res.add(Arrays.asList(nums[i], nums[l], nums[r]));
                    //缩小范围
                    l++;
                    r--;
                    while (l<r&&nums[l]==nums[l-1]){
                        l++;//去重
                    }
                    while (l<r&&nums[r]==nums[r+1]){
                        r--;//去重
                    }

                } else if (sum < 0) {
                    l++;
                } else {
                    r--;
                }

            }
        }
        return  res;

    }

在这里插入图片描述

3 四数之和

四数之和 ---- 题目链接

要求:
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。

3.1 四层for (超时)

    /*
    方法1. 时间复杂度较高 耗时
     */
    public static List<List<Integer>> fourSum1(int[] nums, int target) {
        List<List<Integer>> res = new ArrayList<>();
        for (int i = 0; i < nums.length; i++) {
            for (int j = i + 1; j < nums.length; j++) {
                for (int k = j + 1; k < nums.length; k++) {
                    for (int m = k + 1; m < nums.length; m++) {
                        if (nums[i] + nums[j] + nums[k] + nums[m] == target) {
                            List<Integer> ans = new ArrayList<>();
                            Collections.addAll(ans, nums[i], nums[j], nums[k], nums[m]);
                            Collections.sort(ans);
                            if (!res.contains(ans)) {
                                res.add(ans);
                            }
                        }
                    }
                }
            }
        }
        return res;
    }

在这里插入图片描述

3.2 双层循环和双指针

    /*
    方法2. 利用双层循环和双指针
     */
    //判断时每次循环都计算四数之和是否符合要求
    public List<List<Integer>> fourSum2(int[] nums, int target) {
        List<List<Integer>> res = new ArrayList<>();
        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 l = j + 1;
                int r = len - 1;
                while (l < r) {
                    int sum = nums[i] + nums[j] + nums[l] + nums[r];
                    if (sum == target) {
                        ArrayList<Integer> tmp = new ArrayList<>();
                        Collections.addAll(tmp, nums[i], nums[j], nums[l], nums[r]);
                        res.add(tmp);
                        l++;
                        r--;
                        while (l < r && nums[l] == nums[l - 1]) {
                            l++;
                        }
                        while (l < r && nums[r] == nums[r + 1]) {
                            r--;
                        }
                    } else if (sum < target) {
                        l++;
                    } else {
                        r--;
                    }
                }

            }
        }
        return res;

    }

3.3 优化

    /*
    优化
     */
    public List<List<Integer>> fourSum(int[] nums, int target) {
        int len = nums.length;
        List<List<Integer>> res = new ArrayList<>();
        Arrays.sort(nums);
        if (len < 4) {
            return res;
        }
        long min4 = (long) nums[0] + nums[1] + nums[2] + nums[3]; 	//最小的四数之和
        long max3 = (long) nums[len - 1] + nums[len - 2] + nums[len - 3];
        long sum3 = 0;

        for (int i = 0; i < len - 3; i++) {
            if (min4 > target) {//最小的4个数的和大于目标值
                break;
            }
            if (i + 4 < len) {
                min4 = min4 - nums[i] + nums[i + 4];//向右滑动更新区间和
            }
            if (i > 0 && nums[i] == nums[i - 1]) {//去重
                continue;
            }

            if (max3 + nums[i] < target) {//第一个元素+最大的三个数的和小于目标值
                continue;
            }
            //如果第一个数确定,并加上后面三个连续数的和小于target,结束循环
            sum3 = (long) nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3];


            for (int j = i + 1; j < len - 2; j++) {
                if (sum3 > target) {
                    break;
                }
                if (j + 3 < len) {
                    sum3 = sum3 - nums[j] + nums[j + 3];//更新区间和
                }
                if (j > i + 1 && nums[j] == nums[j - 1]) {//去重
                    continue;
                }
                int l = j + 1;
                int r = len - 1;
                /*第二层循环时,可以确定nums[i]+nums[j]
                        可以先计算出前两数和,再判断 双指针指向的和是否满足要求
                        这样 先确定已知的两个和 再判断 双指针的和 可以比每次都计算出四个数之和要稍快些
                 */
                int t = target - nums[i] - nums[j];

                while (l < r) {
                    int cur = nums[l] + nums[r];
                    if (cur < t) {
                        l++;
                    } else if (cur > t) {
                        r--;
                    } else {
                        List<Integer> ans = new ArrayList<>();
                        Collections.addAll(ans, nums[i], nums[j], nums[l], nums[r]);
                        res.add(ans);
                        l++;
                        r--;
                        //去重 不越界且 当 l 等于它之前(左边)的一个数,说明已经被枚举过了
                        while (l < r && nums[l] == nums[l - 1]) {
                            l++;
                        }
                        //去重 不越界且 当 r 等于它右边的一个数,说明已经被枚举过了
                        while (l < r && nums[r] == nums[r + 1]) {
                            r--;
                        }
                    }
                }
            }
        }
        return res;
    }

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值