LeetCode 👉 HOT 100 👉 三数之和 - 中等题

408 阅读3分钟

这是我参与11月更文挑战的第18天,活动详情查看:2021最后一次更文挑战

题目

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例1

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]

示例2

输入:nums = []
输出:[]

思路

题目要找给定数组 nums 中,所有可能和为 0 的三元组,且三元组不可重复

就是说 [-1, -1, 2] 和 [-1, 2, -1] 是同一三元组,且如果:[nums[i], nums[j], nums[k]] == [nums[I], nums[J], nums[K]] 其中至少有 i != I 、j != J 、k != K 一个等式成立,他们也是相同的三元组

那么大概思路就有:

暴力解法

  • 先将题目给的 nums 数组,按从小到大排序

  • 枚举三元组的第一个元素,再从第一个元素往后的位置,枚举三元组的第二个元素,在同理枚举第三个元素

  • 保留上述符合题目条件的三元组

  • 去重问题,在三元组的第一个元素 nums[i] 被枚举后,无论该元素能否符合成为题目要求的三元组成员,需要去判断 nums[i + 1] 是否和 nums[i] 相等,如果相等,则需将枚举的位置 i + 1,就可以保证三元组的第一个元素不会重复,同理处理第二个元素。在寻找第三个元素的过程中,如果寻找到符合要求的元素,则可以直接退出第三层循环,保证第三个元素不重复

  • 几个提前结束枚举的条件

    • 如果第一个元素枚举之后,就符合 nums[i] > 0,可以直接返回结果(因为一个大于0的数,加上任何比他大的数,都会大于0)

    • 如果第一个元素枚举之后,第二个元素初始化位置 j = i + 1,符合 nums[i] + nums[j] > 0, 也可直接返回结果

    • 在枚举第二个元素的过程中,如果有 nums[i] + nums[j] + nums[n - 1] < 0,说明 j 位置的元素不符合要求,可以跳过 jj + 1

代码如下

    var threeSum = function(nums) {
        let ans = [], n = nums.length;
        // 从小到大排序
        nums.sort((a, b) => a - b);
        for(let i = 0; i < n - 2; i++) {
            // 提前结束枚举 1
            if(nums[i] > 0) {
                return ans;
            }
            let j = i + 1;
            // 提前结束枚举 2
            if(nums[i] + nums[j] > 0) {
                return ans;
            }
            for(j; j < n - 1; j++) {
                // 提前结束枚举 3
                if(nums[i] + nums[j] + nums[n - 1] < 0) {
                    continue;
                }
                let k = j + 1;
                while(k < n) {
                    // 只寻找一次 第三位置上的元素
                    if(nums[i] + nums[j] + nums[k] == 0) {
                        ans.push([nums[i], nums[j], nums[k]]);
                        break;
                    }
                    k++;
                }
                // 第二位置去重
                while(j + 1 < n - 1 && nums[j] == nums[j + 1]) {
                    j++;
                }
            }
            // 第一位置去重
            while(i + 1 < n - 2 && nums[i] == nums[i + 1]) {
                i++;
            }
        }
        return ans;
    }

排序加双指针

对于已排序的数组,在数组中寻找两个数符合某些条件的时候,可以用双指针,因为可以明确的知道指针的移动规则

在寻找三元组,第二、第三元素的过程中,优化上述解法:

  • 定义双指针 left = i + 1right = n - 1

  • left < right 时,判断三数之和 sum = nums[i] + nums[left] + nums[right]

    • sum == 0,将 [nums[i], nums[left], nums[right]] 放入结果,同时将 left 向右移动到下一个不等于 nums[left] 的位置(去重),将 right 向左移动到下一个不等于 nums[right] 的位置,再次比较

    • sum > 0,将 right 向左移动(结果变小)

    • sum < 0,将 left 向右移动(结果变大)

  • 重复上述步骤

代码如下

    /**
     * @param {number[]} nums
     * @return {number[][]}
     */
    var threeSum = function(nums) {
        let ans = [];
        nums.sort((a,b) => {return a - b});
        const n = nums.length;
        for(var i = 0; i < n; i++) {
            if(nums[i] > 0) break;
            if(i > 0 && nums[i] === nums[i - 1]) continue;
            let left = i + 1;
            let right = n - 1;

            while(left < right) {
                const sums = nums[i] + nums[left] + nums[right];
                if(sums == 0) {
                    ans.push([nums[i], nums[left], nums[right]]);
                    while(left < right && nums[left] === nums[left + 1]) left++;
                    while(left < right && nums[right] === nums[right - 1]) right--;
                    left++;
                    right--;
                } else if(sums > 0) {
                    right--;
                } else {
                    left++;
                }
            }
        }
        return ans;
    };

小结

复杂问题,要学会分解;分解之后说不定可以双指针 😂😂😂

LeetCode 👉 HOT 100 👉 三数之和 - 中等题

LeetCode 👉 HOT 100 👉 盛最多水的容器 - 中等题

LeetCode 👉 HOT 100 👉 最长回文子串 - 中等题

LeetCode 👉 HOT 100 👉 无重复字符的最长子串 - 中等题

LeetCode 👉 HOT 100 👉 两数相加 - 中等题

LeetCode 👉 HOT 100 👉 两数之和 - 简单题