15. 三数之和 & 18. 四数之和 & 16. 最接近的三数之和(双指针)& 454. 四数相加 II(哈希表)【M】

15. 三数之和

经典的多数之和题
在这里插入图片描述

这个题之前做过一遍,花了挺久的时间才搞懂,这次重新做能够快速想到思路,但是还是存在细节问题。这题的主要难点是如何去重

基本思路:首先,想暴力解,暴力解就是将所有可能的组合均遍历一遍,那么时间复杂度为: O ( n ∗ ( n − 1 ) ∗ ( n − 2 ) ) = O ( N 3 ) O(n *(n - 1)*(n-2))=O(N^3) O(n(n1)(n2))=O(N3)

这样解,肯定不行,所以需要转变思路,将暴力解优化一下,由于最大的复杂度是 O ( N 3 ) O(N^3) O(N3),那么排序一下也不会影响,所以先sort,然后就能想到针对有序数组,是否可以用二分查找——确定一个数,然后针对这个数后面的子数组,看能否找到符合条件的两个值

所以,我们设定三个变量:i:用来遍历的,left是子数组的头,right是子数组的尾。每次都是确定当前的i,然后再二分查找:二分查找的移动条件是:

  • 如果nums[i] + nums[left] + nums[right] > 0,说明right太大,仅移动right
  • 如果 <0,说明left太小,仅移动left
  • 如果=0,说明是解,那么将其取出,并且同时移动left和right(要同时移动,如果仅移动left or right,如果三数之和仍为0,那么肯定是重复解,即一旦确定两个数,另外一个数的值是确定的)

但是,还存在的是重复解问题:重复存在于2个方面

  • i:前一个值i-1,和当前值i,如果其中一个存在重复,那么他们的解是会存在重复的——需要判断i和i-1的值一样,说明上一次循环已经查找过了,那么i就跳过

  • left和right中:如果当前的left和right是满足要求的解,left++,right–之后,如果指向的值还是一样的,那么必然出现重复解

    eg: -1,-1,-1,0,2,2,可以发现:i=0, left=1, right=5,-1±1+2=0,是一组解;而left++=2,right–=4,那么还是指向了-1,2,所以出现重复

    ——需要left、right循环移动上和上次结果不一样的地方

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        if(nums.length < 3) return res;         // 去除边界情况
        Arrays.sort(nums);
        for(int i = 0; i < nums.length - 2; i++){           // 固定第一个数组
            if(i != 0 && nums[i] == nums[i - 1]) continue; // 如果当前结点的值和前面一个的值一样,跳过这次循环(去重)
            int left = i + 1, right = nums.length - 1;
            while(left < right){
                int temp = nums[i] + nums[left] + nums[right];
                if(temp == 0){
                    res.add(Arrays.asList(nums[i], nums[left], nums[right]));
                    left++;
                    right--;
                    while(left < right && nums[left - 1] == nums[left]) left++;         // 除去可能重复的解
                    while(left < right && nums[right + 1] == nums[right]) right--;
                }
                else if(temp < 0) left++;
                else right--;
            }
        }
        return res;
    }
}

时间复杂度 O ( N 2 ) O(N^2) O(N2)

一般做过一遍,碰到同样的题能够想到思路,但是细节还是要好好打磨的

18. 四数之和

在这里插入图片描述

本题几乎同上题,就是在3数之和外面再套一层循环,本质上还是双指针

当然,最关键的还是去重的问题,外面新增的那一层还是要去重,其余内部均一致。

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> res = new ArrayList<>();
        if(nums.length < 4) return res;     // 排除特殊情况
        Arrays.sort(nums);          // 为了用双指针,先需要排序
        for(int i = 0; i < nums.length - 3; i++){
            if(i != 0 && nums[i] == nums[i - 1]) continue;      // 去重
            for(int j = i + 1; j < nums.length - 2; j++){
                if(j != i + 1 && nums[j] == nums[j - 1])continue;       // 去重
                int left = j + 1, right = nums.length - 1;
                while(left < right){
                    int temp = nums[i] + nums[j] + nums[left] + nums[right];
                    if(temp == target){
                        res.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(temp < target) left++;
                    else right--;
                }
            }
        }
        return res;
    }
}

16. 最接近的三数之和

在这里插入图片描述

本质思想还是双指针,但是变化的是指针的移动条件

具体看代码,因为不需要去重所以难度并不大

class Solution {
    public int threeSumClosest(int[] nums, int target) {
        int res = target, delta = Integer.MAX_VALUE;
        Arrays.sort(nums);
        for(int i = 0; i < nums.length - 2; i++){
            if(i != 0 && nums[i] == nums[i - 1]) continue;      // 去个重
            int left = i + 1, right = nums.length - 1;
            while(left < right){
                int temp = nums[i] + nums[left] + nums[right];
                if(temp == target) return target;       // 找到目标值了,直接返回
                if(Math.abs(target - temp) < delta){            // 如果差值较小,那么更新差值
                    delta = Math.abs(target - temp);
                    res = temp;
                }
                if(temp < target) left++;       // 如果结果太小,移动left;
                if(temp > target) right--;      // 结果太大,移动right
            }
        }
        return res;
    }
}

题解对双指针进行优化:进行了3点优化,我觉得比较有价值的是第2点,每次循环中,都去获取当前的最大值和最小值,最大值出现在:nums[i] + nums[right - 1] + nums[right] (right = len - 1),最小值出现在nums[i] + nums[left] + nums[left+1] (left=i+1),先看如果target比最大值还大,那么最大值一定是最接近的值;如果target比最小值还小,那么最小值一定是最接近的值,那么就能提前结束本次循环。

参考:

https://leetcode-cn.com/problems/3sum-closest/solution/dui-shuang-zhi-zhen-fa-jin-xing-yi-dian-you-hua-da/

454. 四数相加 II

在这里插入图片描述

这题其实和上面3题均不同,反而因为惯性思维,导致做不出来(我就是这样 😦

分析该题,同样是求4数之和,这次是将四个数分别放在4个数组中,那么即使是对每个数组排序,也没法利用双指针进行遍历。

所以,这条路行不通。

能想到的是:暴力解,遍历所有可能的组合,时间复杂度为 O ( N 4 ) O(N^4) O(N4)

根据参考的题解,想到,4个数相加,可以划分成3数之和 +1数,也可以划分成2数之和+2数之和,并且这边由于分在4个数组中,所以3数之和也无法优化,时间复杂度为 O ( N 3 ) O(N^3) O(N3),所以还是将其划分成 2数之和 + 2数之和

A和B组合,求出所有可能的解,存放在哈希表中,C和D组合,求出所有可能的解,看和AB之和哪个能够对上:

注意不能用hashSet,因为不同的index下标的解是不同的

eg:[-1, -1], [-1, 1], [-1, 1], [1, -1]

A+B存在:[-2, 0, -2, 0], C+D存在:[0, -2, 2, 0],他们之间的组合对应的index都是不同的,所以都需要记录

所以A+B在求解时,如果遇到相同的解需要记录出现的次数,而C+D在遍历中,如果出现解,需要加上的是A+B解出现的次数

class Solution {
    public int fourSumCount(int[] A, int[] B, int[] C, int[] D) {
        int len = A.length;
        int res = 0;
        HashMap<Integer, Integer> store = new HashMap<>();       // 存放A、B之和
        for(int i = 0; i < len; i++){                   // 找到A、B所有可能的解,放入哈希集合中
            for(int j = 0; j < len; j++){
                int sum = A[i] + B[j];
                if(store.containsKey(sum)){
                    store.put(sum, store.get(sum) + 1);		// 如果和一样,需要记录出现的次数
                }
                else{
                    store.put(sum, 1);
                }
            }
        }
        for(int i = 0; i < len; i++){
            for(int j = 0; j < len; j++){
                int temp = C[i] + D[j];
                if(store.containsKey(-temp)) res += store.get(-temp);
            }
        }
        return res;
    }
}

总结:这题还是有很多技巧的,之前做过一遍还是没有记住。

参考:

  1. https://leetcode-cn.com/problems/4sum-ii/solution/si-shu-xiang-jia-ii-by-leetcode-solution/
  2. https://leetcode-cn.com/problems/4sum-ii/solution/chao-ji-rong-yi-li-jie-de-fang-fa-si-shu-xiang-jia/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值