16. 最接近的三数之和
给你一个长度为 n 的整数数组 nums 和 一个目标值 target。请你从 nums 中选出三个整数,使它们的和与 target 最接近。
返回这三个数的和。
假定每组输入只存在恰好一个解。
示例 1:
输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。
示例 2:
输入:nums = [0,0,0], target = 1
输出:0
提示:
3 <= nums.length <= 1000
-1000 <= nums[i] <= 1000
-104 <= target <= 104
做算法题,一定要先理解题目。
题目解析
本问题可以用双指针法结合排序解决。首先对数组进行排序,然后遍历每个数,对于每个数,利用两个指针分别指向该数后面的开始和结束位置,通过比较三数之和与目标值的差的绝对值来更新最接近的和。
算法步骤如下:
1、对数组进行排序。
2、遍历排序后的数组,以当前数为第一个数。
3、设立两个指针,分别指向当前数之后的第一个数(left)和数组的最后一个数(right)。
4、如果当前指针组合的和与目标值相等,则直接返回该和,因为不可能有更接近的和了。
5、如果和小于目标值,则将左指针右移,寻找更大的和。
6、如果和大于目标值,则将右指针左移,寻找更小的和。
7、每次比较后,更新最接近的和。
算法优化:
1、避免重复:如果当前数与前一个数相同,则跳过,因为它们的组合会重复。
2、提前停止:如果当前数与后两个数的和已经大于目标值,或者与前两个数的和已经小于目标值,则停止遍历,因为不会有更接近的结果了。
let nums = [-4, -1, 1, 2]
let target = 1;
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var threeSumClosest = function (nums, target) {
// 对数组进行排序,以便于使用双指针法
nums.sort((a, b) => a - b)
let n = nums.length
// 初始化最接近的和为无穷大,以便于后续比较
// let closest_sum = Number.POSITIVE_INFINITY;
let closest_sum = Infinity;
// 初始化最小差值为无穷大
// let min_diff = Number.POSITIVE_INFINITY;
let min_diff = Infinity;
let sum3 = 0;
let current_sum = 0;
console.log("进入")
// 遍历数组,固定第一个数
for (let i in nums) {
// 跳过重复元素,避免重复计算
if (i > 0 && nums[i] == nums[i - 1]) {
continue
}
console.log("进入循环", i)
// 检查当前固定数与后面两个最小数的和,如果已经大于target,则后续不再有更小的和,可以提前结束循环
if (nums[i] + nums[i + 1] + nums[i + 2] > target) {
sum3 = nums[i] + nums[i + 1] + nums[i + 2]
if (Math.abs(sum3 - target) < Math.abs(closest_sum - target)) {
closest_sum = sum3
console.log("进入循环------1")
continue
}
}
// 检查当前固定数与后面两个最大数的和,如果已经小于target,则当前固定数不可能组合出更接近target的和,继续尝试下一个数
if (nums[i] + nums[n - 1] + nums[n - 2] < target) {
sum3 = nums[i] + nums[n - 1] + nums[n - 2]
if (Math.abs(sum3 - target) < Math.abs(closest_sum - target)) {
closest_sum = sum3
console.log("进入循环------2")
continue
}
}
// 设置左右指针
let left = Number(i) + 1;
let right = n - 1;
console.log(left)
console.log(right)
while (left < right) {
current_sum = nums[i] + nums[left] + nums[right]
diff = target - current_sum
console.log("进入循环------3")
// 如果差值为0,直接返回当前和,这就是最优解
if (diff == 0) {
console.log("进入循环------4")
return current_sum
}
// 如果当前和更接近目标值,则更新最接近的和和最小差值
if (Math.abs(diff) < min_diff) {
console.log("进入循环------5")
min_diff = Math.abs(diff)
closest_sum = current_sum
}
// 根据差值去调整左右指针,以寻求更接近的和
if (diff > 0) {
console.log("进入循环------6")
left += 1
} else {
console.log("进入循环------7")
right -= 1
}
}
}
console.log("进入循环------8")
return closest_sum
};
console.log(threeSumClosest(nums, target))
总结
本文讨论的是一类常见的算法问题——在给定的数组中找到三个数,使得这三个数的和最接近目标值。这类问题属于数组处理和优化搜索问题,特别是当我们需要从多个数中选择一部分组合,以满足某种准则时,此类方法特别适用。
**解决这个问题的算法是排序加双指针法。**这个算法非常适合用于解决数组中的两数或三数之和问题,尤其是当数组较大时,能够有效减少不必要的计算,避免暴力枚举的高时间复杂度。双指针法的基本思想是通过对数组的排序,然后利用数组的有序性,通过左右指针向中间移动的方式,来快速定位和调整数值,以找到最优解。
具体到本题,排序确保了我们可以用线性的方式来调整指针,以增大或减小当前的数值和。当我们固定了第一个数之后,剩余的两个数我们通过左右指针来尝试,这样就能在 O(n) 的时间内找到与当前固定数配对的最佳组合。由于我们需要遍历每一个数作为可能的第一个数,因此总的时间复杂度是 O(n^2)。
在本题的算法实现中,我们还加入了一些优化策略:
跳过重复值:由于排序后的数组中相同的数是连续的,我们可以跳过一些重复的计算,这在有大量重复数值的数组中特别有效。
提前终止循环:在某些情况下,我们可以提前知道不需要继续移动指针,因为不可能得到更接近的结果,这样可以省去不必要的计算。
这个算法特别适合于那些需要从数组中找出数值和最接近某个给定值的问题,无论是两数之和、三数之和,还是更多数的和。它的优势在于高效的搜索能力和较低的时间复杂度。对于类似的问题,如求绝对差最小、求最大或最小的和等,只要涉及到数组的和的优化搜索,都可以考虑使用排序加双指针法来解决。
在实际的软件开发中,类似的算法可以用于优化搜索问题,如在电商网站中寻找价格最接近用户预算的商品组合,在旅行路线规划中寻找最优的交通工具组合等。掌握这类算法,能够帮助开发者构建更加高效和智能的功能。