【力扣】16. 最接近的三数之和

311 阅读3分钟

题目

给你一个长度为 n 的整数数组 nums 和 一个目标值 target。请你从 nums 中选出三个整数,使它们的和与 target 最接近。

返回这三个数的和。

假定每组输入只存在恰好一个解。

image.png

一、题目分析

  • 【长度】为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;
};

果然不出意外~~~超时了~~~

image.png

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;
};

果然不出意外😯优化后的代码能成功跑过所有测试用例了👏👏👏~~

image.png

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;
};

果然不出意外的,有快了一点点~~~👏👏👏

image.png

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;
};

居然有小惊喜👏👏👏~~~~

image.png

😄未完待续~~~

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;
};

第二种速度提升了不少~~~~👏👏👏

image.png

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;
};

果然~快了不少~👌

image.png

三、最后

【题目地址】(leetcode-cn.com/problems/3s…)

😄未完待续~~~