Leetcode【固定左边界+双指针】| 15. 三数之和 & 16. 最接近的三数之和

Leetcode | 15. 三数之和

题目

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例:
 给定数组 nums = [-1, 0, 1, 2, -1, -4],
 满足要求的三元组集合为:
 [
 [-1, 0, 1],
 [-1, -1, 2]
 ]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/3sum

解题

排序+双指针

两数之和的双指针解法引申而来。
三个数的双指针这样设定:

  1. 首先将数组排序,以便之后双指针的移动;
  2. 然后使用一层循环固定左边界 first 从0到n-3(第一个数);这样就变成了剩下范围内的两数之和问题:另外的两个数由双指针secondthird分别从i+1到n-1的两头开始进行双指针的移动。
  3. 移动规则如下:sum = nums[first]+nums[second]+nums[third]
    ① 如果 sum>target,r左移,使总和更小更接近target(左移后还与原来重复的数字跳过)
    ② 如果 sum<target,l右移,使总和更大更接近target(右移后还与原来重复的数字跳过)
    ③ 如果 sum == target,返回结果。

  此题中target = 0。

代码参考 力扣官方题解

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        //排序+双指针
        int n = nums.length;
        Arrays.sort(nums);
        List<List<Integer>> res = new ArrayList<List<Integer>>();
        // 枚举 a
        for (int first = 0; first < n; ++first) {
            // 需要和上一次枚举的数不相同
            if (first > 0 && nums[first] == nums[first - 1]) {
                continue;
            }
            // c 对应的指针初始指向数组的最右端
            int third = n - 1;
            int target = -nums[first];
            // 枚举 b
            for (int second = first + 1; second < n; ++second) {
                // 需要和上一次枚举的数不相同
                if (second > first + 1 && nums[second] == nums[second - 1]) {
                    continue;
                }
                // 需要保证 b 的指针在 c 的指针的左侧
                while (second < third && nums[second] + nums[third] > target) {
                    --third;
                }
                // 如果指针重合,随着 b 后续的增加
                // 就不会有满足 a+b+c=0 并且 b<c 的 c 了,可以退出循环
                if (second == third) {
                    break;
                }
                if (nums[second] + nums[third] == target) {
                    List<Integer> list = new ArrayList<Integer>();
                    list.add(nums[first]);
                    list.add(nums[second]);
                    list.add(nums[third]);
                    res.add(list);
                }
            }
        }
        return res;
    }
}

排序+哈希

两数之和的哈希解法引申而来。
因为给定目标和,求目标和为target的三个数,所以可以遍历时利用哈希表来进行求解,但是效率不高。
同样先排序+一重循环固定左边界,问题就变成了从左边界到最右边之间的两数之和的求解问题,这里采用两数之和的哈希求解法。

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> res = new ArrayList<List<Integer>>();
        //判断特殊情况
        if(nums.length<3) return res;
        // if(nums.length == 3){
        //     if(nums[0]+nums[1]+nums[2] == 0){
        //         List<Integer> tmp = new ArrayList<>();
        //         tmp.add(nums[0]);
        //         tmp.add(nums[1]);
        //         tmp.add(nums[2]);
        //         res.add(tmp);
        //     }
        //     return res;
        // }
        
        Arrays.sort(nums);
        for(int i = 0; i < nums.length;i++){
            if(nums[i]>0) break;
            if(i>=1 && nums[i] == nums[i-1]){
                continue;
            }
            if(nums[i] == 0){
                if(i+2<nums.length && nums[i+1] == 0 && nums[i+2] == 0){
                    List<Integer> tmp = new ArrayList<>();
                    tmp.add(0);
                    tmp.add(0);
                    tmp.add(0);
                    res.add(tmp);
                }
                continue;
            }
            if(nums[i]<0){
                Set<Integer> numset = new HashSet<>();
                Set<Integer> s = new HashSet<>(); 
                for(int j = i+1; j < nums.length; j++){
                    if(!s.contains(nums[j]) && numset.contains(0-nums[i]-nums[j])){
                        List<Integer> tmp = new ArrayList<>();
                        tmp.add(nums[i]);
                        tmp.add(nums[j]);
                        tmp.add(0-nums[i]-nums[j]);
                        res.add(tmp);
                        s.add(nums[j]);
                    }else{
                        numset.add(nums[j]);
                    }
                    
                }
            }
        }
        return res;
    }
}

在这里插入图片描述

Leetcode | 16. 最接近的三数之和

题目

给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。
示例:
 输入:nums = [-1,2,1,-4], target = 1
 输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。
提示:
 3 <= nums.length <= 10^3
 -10^3 <= nums[i] <= 10^3
 -10^4 <= target <= 10^4
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/3sum-closest

解题

思路(排序+双指针)

同上,由两数之和的双指针解法引申而来。
排序后,先固定左边界(一重循环第一个数 i),再在剩下的范围内(i+1到n-1)解决两数之和的问题。
因为此题与之前求和问题不同的是,这题里可能含有不恰好等于target的情况,所以要求的是最接近于target的情况,就不能用哈希查找的方式了,只能采用双指针方法枚举各个情况逼近target,每种情况都用差值dif = Math.abs(sum-target)来判断,寻找差值最小的sum即为所求结果。
详解见注释。

Java实现

class Solution {
    public int threeSumClosest(int[] nums, int target) {
        //排序+双指针
        Arrays.sort(nums);
        int res = nums[0]+nums[1]+nums[2];
        int dif = Math.abs(res-target);//当前和与目标的差值,求差值最小的res
        if(nums.length==3) return res;
        //一重循环代表第一个数i,双指针从i+1到n-1两头出发,分别代表第二和第三个数
        for(int i = 0; i <= nums.length-3;i++){
            //如果当前数和上一个数一样的话,可以不用再枚举一遍,之后的所有情况在上一轮已经枚举过了
            if(i>0 && nums[i]==nums[i-1]){
                continue;
            }
            int l = i+1 , r = nums.length-1;
            while(l<r){
                if(nums[i]+nums[l]+nums[r]==target){
                    return res = target;
                }
                if(Math.abs(nums[i]+nums[l]+nums[r]-target)<dif){
                    res = nums[i]+nums[l]+nums[r];
                    dif = Math.abs(res-target);
                }
                if(nums[i]+nums[l]+nums[r]>target){
                    int r0 = r-1;
                    //r左移的过程中将一样的r值跳过,道理同上
                    while(l<r0 && nums[r0] == nums[r]){
                        r0--;
                    }
                    r = r0;
                }else{
                    int l0 = l+1;
                    while(l0<r && nums[l0] == nums[l]){
                        l0++;
                    }
                    l = l0;
                }
            }
        }
        return res;
    }
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值