题目
给你一个长度为 n 的整数数组 nums 和 一个目标值 target。请你从 nums 中选出三个整数,使它们的和与 target 最接近。
返回这三个数的和。
假定每组输入只存在恰好一个解。
一、题目分析
- 【长度】为n的整数数组nums
- 【目标值】target
- 【选出】nums中的三个整数,【相加】和与target最接近
二、开始解题
2-1、【穷举法】3个整数嘛,成年人我全都要🤮~
1、第一种尝试最笨的方法,就是把数据可能产生的三个数的所有的组合,全部整合一遍,统一计算距离,再按距离重小到大重排~为数组的第一位0的不就是距离最小、最近的啦~~~~
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var threeSumClosest = function(nums, target) {
function countFun(list) {
let count = list.reduce((pre, next) => {
pre+=next
return pre;
}, 0)
return count;
}
if (nums.length <= 3) return countFun(nums);
let arr = nums;
let sumArr = [];
// 寻找所有组合
let mapCombination = (index, mapArr) => {
let oneArr = [...mapArr];
oneArr.splice(index, 1);
for (let i=0;i<oneArr.length;i++) {
let twoArr = [...oneArr];
twoArr.splice(i, 1);
for (let j=0;j<twoArr.length;j++) {
let itemArr = [mapArr[index], oneArr[i], twoArr[j]];
itemArr.sort((a, b) => a - b);
itemArr = itemArr.toString();
if (!sumArr.find(item => item === itemArr)) {
sumArr.push(itemArr)
}
}
}
};
for (let i = 0; i < arr.length; i++) {
mapCombination(i, arr);
}
// 对所有组合进行求解
let countArr = [];
for (let i = 0;i<sumArr.length;i++) {
let item = sumArr[i];
let setCount = {
list: item.split(',').map(num=>Number(num)),
count: 0,
distance: 0,
}
setCount.count = countFun(setCount.list);
setCount.distance = target - setCount.count > 0 ? target - setCount.count : (target - setCount.count) * -1;
countArr.push(setCount)
}
// 按离 target 的距离进行排序
countArr.sort((a, b) => a.distance - b.distance);
// 得到距离最近的三个数的和
return countArr[0] ? countArr[0].count : 0;
};
果然不出意外~~~超时了~~~
2、发现问题就要解决问题,上一种方案每次用 splice 去截取一个非当前数字的新数组着实有点傻,即耗时又耗力,得到数组后才去求解,感觉多此一举了~【审题审题,题目要的是求和~谁管你数组长啥样~有多少种组合~】,ok~知道问题所在,调整代码,去除不必要的存储新数组的相关代码,直接求最小距离的三位数之和。
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var threeSumClosest = function(nums, target) {
// 列表求和~
function countFun(list) {
let count = list.reduce((pre, next) => {
pre+=next
return pre;
}, 0)
return count;
}
// 干掉那些浑水摸鱼的~
if (nums.length <= 3) return countFun(nums);
let arr = nums;
let minDistance = 10000000000; // 距离能大于100亿~~我认~~
let minCount = 0; // 最小的三位数的和
let mapCombination = (index, mapArr) => {
let oneArr = [...mapArr];
oneArr.splice(index, 1);
for (let i=0;i<oneArr.length;i++) {
let twoArr = [...oneArr];
twoArr.splice(i, 1);
for (let j=0;j<twoArr.length;j++) {
let count = mapArr[index] + oneArr[i] + twoArr[j];
let distance = target - count > 0 ? target - count : (target - count) * -1;
if (distance <= minDistance) {
minDistance = distance;
minCount = count;
}
}
}
};
for (let i = 0; i < arr.length; i++) {
mapCombination(i, arr);
}
return minCount;
};
果然不出意外😯优化后的代码能成功跑过所有测试用例了👏👏👏~~
3、前一种方法虽然跑过了所有测试用例~但这运行时间~~也实在是太久了吧,问题肯定还是出在【splice】去截取新数组上~不行,还是要去掉才行~~
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var threeSumClosest = function(nums, target) {
function countFun(list) {
let count = list.reduce((pre, next) => {
pre+=next
return pre;
}, 0)
return count;
}
if (nums.length <= 3) return countFun(nums);
let arr = nums;
let minDistance = 100000000000;
let minCount = 0;
let mapCombination = (index, mapArr) => {
for (let i=0;i<mapArr.length;i++) {
// 三位数的第一位不能出现已有重复项
if (i !== index) {
for (let j=0;j<mapArr.length;j++) {
// 三位数的第三位不能和第二位与第一位出现已有重复项
if (j !== i && j !== index) {
let count = mapArr[index] + mapArr[i] + mapArr[j];
let distance = target - count > 0 ? target - count : (target - count) * -1;
if (distance <= minDistance) {
minDistance = distance;
minCount = count;
}
}
}
}
}
};
for (let i = 0; i < arr.length; i++) {
mapCombination(i, arr);
}
return minCount;
};
果然不出意外的,有快了一点点~~~👏👏👏
4、但还是有问题~还是太久~~~如果nums传进来一个无序的数组,先重排一下再开始跑计算,distance也不用那么大,可以缩短点距离~
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var threeSumClosest = function(nums, target) {
function countFun(list) {
let count = list.reduce((pre, next) => {
pre+=next
return pre;
}, 0)
return count;
}
nums.sort((a, b) => a - b); // 数据进来后,先重排一下~~小惊喜
if (nums.length <= 3) return countFun(nums);
let arr = nums;
let minDistance = 200;
let minCount = 0;
let mapCombination = (index, mapArr) => {
for (let i=0;i<mapArr.length;i++) {
if (i !== index) {
for (let j=0;j<mapArr.length;j++) {
if (j !== i && j !== index) {
let count = mapArr[index] + mapArr[i] + mapArr[j];
let distance = target - count >= 0 ? target - count : (target - count) * -1;
if (distance <= minDistance) {
minDistance = distance;
minCount = count;
}
}
}
}
}
};
for (let i = 0; i < arr.length; i++) {
mapCombination(i, arr);
}
return minCount;
};
居然有小惊喜👏👏👏~~~~
😄未完待续~~~
2-2、【双指针法】
1、换一种思路思考一下题目,【穷举法】需要列出所有可能性,然后去一一判断对比。因为数组已经从小到大排序过了,在我们遍历数组的时候,去判断我们累加的值小于目标值的关系,判断通过移动指针来实现累加,如果累加和等于目标值,那肯定就是距离最近的了,直接返回即可。
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var threeSumClosest = function(nums, target) {
function countFun(list) {
let count = list.reduce((pre, next) => {
pre+=next
return pre;
}, 0)
return count;
}
if (nums.length <= 3) return countFun(nums);
// 2、【双指针】
nums.sort((a, b) => a - b); // 数据进来后,先重排一下顺序
let arr = nums;
let sumArr = []; // 统计可能性
let minCount = 0;
let minDistance = 200;
for (let i=0;i<arr.length;i++) {
let stop = false;
let [start, end] = [i + 1, arr.length - 1];
while (start < end) {
// 统计可能性
let item = [arr[i], arr[start], arr[end]];
sumArr.push(item);
let count = arr[i] + arr[start] + arr[end];
let distance = Math.abs(count - target);
if (distance <= minDistance) {
minDistance = distance;
minCount = count;
}
// 因为之前排序过,所以去判断三位数的累加和于目标值的关系来判断坐标移动
// 数字累加之和小于目标值,则开始坐标后移
if (count < target) {
start++;
}
// 数字累加之和大于目标值,则结束坐标前移
else if (count > target) {
end--;
}
// 如果等于,则肯定是最小了,距离为0
else {
minCount = count;
stop = true;
break;
}
}
if (stop) break;
}
return minCount;
};
第二种速度提升了不少~~~~👏👏👏
2、第一步忘记把“统计可能性”给删除了~~~数组的操作还是很耗时间的~
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var threeSumClosest = function(nums, target) {
function countFun(list) {
let count = list.reduce((pre, next) => {
pre+=next
return pre;
}, 0)
return count;
}
if (nums.length <= 3) return countFun(nums);
// 2、【双指针】
nums.sort((a, b) => a - b); // 数据进来后,先重排一下顺序
let arr = nums;
let minCount = 0;
let minDistance = 200;
for (let i=0;i<arr.length;i++) {
let stop = false;
let [start, end] = [i + 1, arr.length - 1];
while (start < end) {
let count = arr[i] + arr[start] + arr[end];
let distance = Math.abs(count - target);
if (distance <= minDistance) {
minDistance = distance;
minCount = count;
}
// 因为之前排序过,所以去判断三位数的累加和于目标值的关系来判断移动
// 数字累加之和小于目标值,则开始后移
if (count < target) {
start++;
}
// 数字累加之和大于目标值,则结束前移
else if (count > target) {
end--;
}
// 如果等于,则肯定是最小了,距离为0
else {
minCount = count;
stop = true;
break;
}
}
if (stop) break;
}
return minCount;
};
果然~快了不少~👌
三、最后
【题目地址】(leetcode-cn.com/problems/3s…)
😄未完待续~~~