ID:15.三数之和

75 阅读2分钟

考点:双指针

题目链接

思路

参考官方题解,一步一步从三重循环优化。

首先三重循环的时间复杂度太高,而且最后的结果不能有重复数组,又进一步提高了空间复杂度。

其次考虑到一点:结果需要的是三个数之和为0,如果数组是有序的,当第一层循环遍历到数组元素大于0的时候可以直接结束循环,同时还可以摒弃掉一些重复元素的影响,因此第一步必然是排序。

然后考虑一下里面的两层循环:当一、二层循环固定元素 a、b 的时候,第三层循环确定了一个元素 c,当 a 或 b 增加的时候,c 必然减小,所以二层循环与三层循环是关联的,因而可以用两个指针代替二、三层循环,这样就简化了时间复杂度。此时要分三种情况:1,三数之和小于 0,则左指针自增;2,三数之和等于 0,结果加入三个数的数组,同时左右指针分别自增及自减;3,三数之和大于 0,右指针自减。注意这三种情况下都要考虑重复元素的情况,即当前元素与右边(左指针)/左边(右指针)的是否相等,如果相等就自增/自减到不等为止。

(p.s. 虽然官方题解一直被吐槽晦涩难懂,但是本题的官方题解很详细且能懂,值得细看)

遍历过程可以参考这个题解

var threeSum = function(nums) {
    // 排序
    nums.sort((a, b) => a - b);
    let i, j; // 左右指针
    const res = []; // 结果数组
    for(let k = 0; k < nums.length - 2; k++) {
        // 如果第一层循环的元素已经大于0了,那么后面的元素也必然大于0
        // 此时没有继续循环的必要了,直接跳出
        if(nums[k] > 0) break;
        // 如果遇到重复元素,一直跳到最后一个
        if(k > 0 && nums[k] === nums[k - 1]) continue;
        // 左右指针初始化
        i = k + 1;
        j = nums.length - 1;
        while(i < j) {
            // 保存三数之和
            const s = nums[i] + nums[j] + nums[k];
            // 三数之和小于0
            if(s < 0) {
                // 左指针自增
                i++;
                // 跳过重复元素
                while(i < j && nums[i] === nums[i - 1]) i++;
            }
            // 三数之和等于0
            else if(s === 0) {
                // 加入结果数组,同时左右指针向中间移动
                res.push([nums[i++], nums[j--],nums[k]]);
                // 跳过重复元素
                while(i < j && nums[i] === nums[i - 1]) i++;
                while(i < j && nums[j] === nums[j + 1]) j--;
            }
            // 三数之和大于0
            else {
                // 右指针自减
                j--;
                // 跳过重复元素
                while(i < j && nums[j] === nums[j + 1]) j--;
            }
        }
    }
    return res;
};