三数之和,我如何从超时优化到AC的

这个题很多小伙伴都有遇到过,它也是比较高频的面试题,题目如下:

从题目我们可以知道,答案必须符合以下3个条件:

  • 返回的最终结果一定是一个二维数组。
  • 每一个子数组的长度必须是3,并且每个子数组里的所有项加起来,必须等于0。
  • 子数组之间不能重复,必须唯一(其实这个可以不用管)。

超时版本

3数之和嘛,那就3个循环,如下:

javascript 复制代码
var threeSum = function(nums) {
    if (nums.length < 3){
        return [];
    }
    let result = [];
    for (let index = 0; index < nums.length; index++){
        // 当前的值
        let curItem = nums[index];
        // 剩余的值
        let rest = 0 - curItem;
        // 那接下来的问题就是,剩余的数组中,2数之和呗
        for (let nextIndex = index + 1; nextIndex < nums.length; nextIndex++){
            // 最终剩余的值
            let finalNum = rest - nums[nextIndex];
            // 在剩余的数组中查找最后的值
            for (let finalIndex = nextIndex + 1; finalIndex < nums.length; finalIndex++){
                let temp = [];
                if (nums[finalIndex] === finalNum){
                    temp.push([
                        nums[index],
                        nums[nextIndex],
                        nums[finalIndex]
                    ]);
                    result.push(temp);
                }
            }
        }
    }
    return result;
}

上面这3个循环肯定是超时的,所以我们现在要想一下,如何减少一层循环。

AC版本

我们可以先这么想一下,假如有如下数组:

javascript 复制代码
let arr = [-3, -2, -1, 0, 1, 2, 3]

那上面的数组里,每个子项都可以作为子数组的开头,比如 -3作为开头,那么符合条件的子数组可能是:

javascript 复制代码
[-3, 0, 3]

-2作为开头,那么符合条件的子数组就有可能是:

javascript 复制代码
[-2, 0, 2]

那确定了上面的思想后,第一层遍历的含义也就确定了,就是让每个子项都作为子数组的第一项。

接下来,在上一步的基础上,我们可以使用首尾指针的方式来解决这道题目,具体如下:

javascript 复制代码
let left = index + 1;
let right = nums.length - 1;

// 说明此时的 nums[index], nums[left], nums[right]应该push到数组里 
nums[index] + nums[left] + nums[right] === 0

// 如果大于0,说明 right指针应该减小,这样 以后的数组之和 才有可能 === 0
nums[index] + nums[left] + nums[right] > 0

// 如果相加小于0,说明 left指针应该增大,这样 以后的数组之和 才有可能 === 0
nums[index] + nums[left] + nums[right] < 0

特别提醒:

对于上述的3种情况,上述情况的成立需要满足以下规则:

1、index < left < right。

2、numsindex <= numsleft <= numsright

为了满足上面的2个规则,我们可以在一开始就对数组进行从小到大的排序。

有了上面的理解,我们的代码就比较好写了:

javascript 复制代码
var threeSum = function(nums) {
    if (nums.length < 3){
        return [];
    }
    
    // 从小到大排序
    nums.sort(
        (a, b) => a - b
    );
    
    let result = [];
    
    // 这里需要解释一下,为什么 index < nums.length - 2 ?
    // 因为我们的子数组长度始终为3,当 index === nums.length - 2时,子数组的长度不可能为3
    for (let index = 0; index < nums.length - 2; index++){
        let left = index + 1;
        let right = nums.length - 1;
        while(left < right){
            let curAllNum = nums[index] + nums[left] + nums[right];
            if (curAllNum === 0){
                // 1、如果满足条件,则加入到数组里
                result.push([
                   nums[index],
                   nums[left],
                   nums[right]
                ]);
                left++;
                right--;
            }
            
            if (curAllNum < 0){
                // 2、说明left应该增大,这样才有可能找到index开头的,并且符合答案的子项
                left++;
            }
            
            if (curAllNum > 0){
                // 3、说明right应该减小,这样才有可能找到index开头的,并且符合答案的子项
                right--;
            }
        }
    }
    
    return result;
}

这一版本,leetcode虽然可以ac,但是无论用时,还是内存消耗情况,它都处于下游。那有没有其他更高效的方案呢?还请各位大佬在评论区里大显身手。

本期的分享到这里就结束了,我们下期再见啦~~

相关推荐
程序员黑豆7 小时前
Java 注释详解:单行、多行与文档注释的完整指南
java·前端·ai编程
剪刀石头布啊8 小时前
antd中可编辑表格中巧用useWatch实现联动高性能效果,以及定制延伸学习
前端
油丶酸萝卜别吃8 小时前
前端转全栈学习路线
前端·学习
浮生望8 小时前
前端路由进阶:History API原理与手写HistoryRouter
前端
江畔柳前堤8 小时前
大语言模型分布式训练:从并行策略到万卡工程的系统梳理
人工智能·分布式·深度学习·算法·目标检测·机器学习·语言模型
陆枫Larry9 小时前
JavaScript 中的竞态是什么,为啥会有竟态?
前端
Forever Nore10 小时前
LeetCode 4 寻找两个正序数组的中位数 - 二分
算法·leetcode
上海安当技术11 小时前
半天接入:USBKey RESTful API + C 动态库,Web 和 C/S 两套集成路径实战
前端·后端·restful·集成·usbkey
罗西的思考11 小时前
【OpenClaw具身硬件】MiniClaw 阅读笔记---(1)基础
人工智能·算法·机器学习
DevUI团队11 小时前
从“即兴创作”到“规格先行”,华为云码道(CodeArts)代码智能体持续深耕企业级规范驱动开发能力
前端·人工智能·后端