16. 3Sum Closest(最接近的三数之和)三种解法(C++ & 注释)

本文探讨了解决“最接近的三数之和”问题的两种高效算法:双指针法与二分查找法。通过固定部分元素,利用指针或二分查找优化搜索过程,实现快速找到与目标值最接近的三个整数之和。

1. 题目描述

给定一个包括 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

题目链接:中文题目英文题目

2. 暴力解法(Brute Force, Time Limit Exceeded)

直接三个for循环检查所有三元组是否符合题意,这种方法和3Sum题目的暴力解法差不多思路,代码可以参考一下,就不单独列举代码了。

3. 双指针(Two Pointers)

3.1 解题思路

这里的双指针思路和3Sum题目的差不多,大家可以参考一下之前题目的讲解思路。不同的是,可能没有三数和等于target的情况,所以我们需要考察所有三元组与target的差值,如果为负数,三数和小了,左边界右移动;如果为正数,三数和大了,右边界左移动;如果等于0,直接返回target,不用检查后面的三元组。然后记录一下三数和和target的最小差值,因为正数和负数都符合题意,所以用绝对值来进行比较。

如果还不是很明白的话,可以参考一下这里面的动图,加深一下理解。

3.2 实例代码

class Solution {
public:
    int threeSumClosest(vector<int>& nums, int target) {
        int len = nums.size(), difference = INT_MAX;
        sort(nums.begin(), nums.end());

        for (int i = 0; i < len; i++) {
            if (i != 0 && nums[i] == nums[i - 1]) continue; //跳过nums[i]代表的重复数字
            int left = i + 1, right = len - 1;
            while (left < right) {
                int sum = nums[i] + nums[left] + nums[right];
                if (abs(target - sum) < abs(difference)) difference = target - sum;
                if (target > sum) left++;
                else if (target < sum) right--;
                else return target; // 找到了三数和等于target,最完美的情况,无需检查其他情况,直接返回target即可
            }
        }

        return target - difference;
    }
};

4. 二分查找(Binary Search)

4.1 解题思路

上面3. 双指针(Two Pointers)的方法是先固定一个数,然后从左右两边查找与target距离最小的剩余两个值。那么,我们可不可先固定两个数,然后用二分查找查找第三个数呢?

这里的思路和二分精确查找一个数比较类似,只是可能找不到使nums[i] + nums[j] + nums[k] = target的k,所以我们调整一下思路,转而从j + 1开始查找第一个比target - nums[i] - nums[j]大的数a(序号k),然后后退一个序号k - 1(数b),在排除k = 数组长度,或者k - 1 = j后,数a和数b就是左右两边离target - nums[i] - nums[j]最近的数,也就是三数和最接近target的数,之后我们只要记录距离最短的差值(difference),最后返回target - difference即可

当然,如果找到等于target - nums[i] - nums[j]的数,也就是target - nums[i] - nums[j] - nums[k] = 0,差值为0,这个时候结束循环,返回target即可(相当于target - 0);

PS:关于lower_bound( )和upper_bound( )的常见用法

4.2 实例代码


class Solution {
public:
    int threeSumClosest(vector<int>& nums, int target) {
        int len = nums.size(), difference = INT_MAX;
        sort(nums.begin(), nums.end());

        for (int i = 0; i < len && difference != 0; i++) {
            if (i != 0 && nums[i] == nums[i - 1]) continue; //跳过nums[i]代表的重复数字
            for (int j = i + 1; j < len; j++) {
                if (j != i + 1 && nums[j] == nums[j - 1]) continue; //跳过nums[j]代表的重复数字
                int complement = target - nums[i] - nums[j];
                vector<int>::iterator it = upper_bound(nums.begin() + j + 1, nums.end(), complement); // 找到第一个比complement大于或等于的数字,并返回该地址
                int right = it - nums.begin(), // 计算第一个比complement大于或等于的数字序号
                    left = right - 1; // 计算第一个比complement小的数字序号
                if (right < len && abs(complement - nums[right]) < abs(difference)) difference = complement - nums[right];
                if (left > j && abs(complement - nums[left]) < abs(difference)) difference = complement - nums[left];
            }
        }

        return target - difference;
    }
};

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值