【数据结构与算法】力扣 15. 三数之和

77 阅读4分钟

题目描述

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

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

示例 1:

输入: nums = [-1,0,1,2,-1,-4]
输出: [[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1][-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。

示例 2:

输入: nums = [0,1,1]
输出: []
解释: 唯一可能的三元组和不为 0 。

示例 3:

输入: nums = [0,0,0]
输出: [[0,0,0]]
解释: 唯一可能的三元组和为 0

提示:

  • 3 <= nums.length <= 3000
  • -105 <= nums[i] <= 105

分析解答

乍眼一看,求和问题。直接上哈希表来做。然后,去重?去重!百思不得其解,遂跟着代码随想录的解答学习了一番。

以下思路为代码随想录的解答

思路拓展

1632108374-OljZRm-15.三数之和.gif (376×246) (leetcode-cn.com)

image.png

这道题主要使用双指针法。先将整个数组排好序(题干说明:输出的顺序和三元组的顺序并不重要,所以可以排序),i 为起始位置,l 左指针,r 右指针。目的是让 nums[i] + nums[left] + nums[right] = 0 ,i 是for 循环遍历的索引。

那么怎么满足这个条件呢?可以先分类讨论一下:

  • 初始位置,如果和大于 0,那么 right--;如果小于 0, 那么 left++;直到和为 0
  • 然而,因为我们是正序排列的数组,所以初始 i 如果大于0,那么直接返回 []

第一个问题解决了,然后我们开始去重。

首先抛出一个难点,此时我们遍历到第 i 个元素,想要去重,那是应该判断 i 和 i + 1 还是 i 和 i - 1 呢?(尽管他们乍眼一看没什么区别😥)

首先思考,[0, 0, 0]是满足条件的,他们内部相等是被允许的。也就是说 i 和 left 相等是允许的,也就是说 i 和 i + 1 是可以相等的,所以应该采用 i 和 i - 1 来去重。

image.png

开始 i -> 1位置, l -> 2位置,r -> 4位置 [-1, -1, 2] 合理。然后 i -> 2位置, l -> 3位置,r -> 4位置,[-1, -1, 2] 和之前的冲突,需要去重,这轮 i 循环直接 continue。

所以前面先对整个数组排序是多么重要,妙啊~~~

之后对 left 和 right 的去重都是同样的操作。

然后就是完整代码了,再理一遍思路:

  • 数组排序,使用排序后的数组
  • 第一次循环数组,i 是数组的每一个值,也是 arr 中可能的每个三元组的第一个值(最小值)的索引
  • sortedNums[i](最小值) 如果大于 0 直接 return
  • 去重 i 项,可能的三元组中的第一项
  • 内部循环,主要用来找到 l 和 r 的值(三元组中剩下那两项的值),条件是 l < r
  • 需要先判断大于和小于 因为不满足条件直接进行指针移动 而相等的情况下还需要 进行去重
  • 当找到sortedNums[i] + sortedNums[l] + sortedNums[r] === 0 满足条件时,直接push
  • push 的同时,去重,l和 l + 1,r 和 r -1 进行比较,进行指针移动
  • 再同时,去重了之后,i 不动,移动 l 和 r , l++ , r--
/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var threeSum = function (nums) {
    let res = []
    let sortedNums = nums.sort((a, b) => a - b)
    for (let i = 0; i < sortedNums.length; i++) {
        let l = i + 1, r = sortedNums.length - 1
        // 最小值大于 0 直接 return
        if (sortedNums[i] > 0) return res
        // 去重 i 项
        if (sortedNums[i] === sortedNums[i - 1]) continue
        while (l < r) {
            // 需要先判断大于和小于 因为不满足条件直接进行指针移动 而相等的情况下还需要 进行去重
            if (sortedNums[i] + sortedNums[l] + sortedNums[r] > 0) {
                r--
            } else if (sortedNums[i] + sortedNums[l] + sortedNums[r] < 0) {
                l++;
            } else {
                res.push([sortedNums[i], sortedNums[l], sortedNums[r]])
                // 去重 l 和 r 项
                while (l < r && nums[l] === nums[l + 1]) {
                    l++
                }
                while (l < r && nums[r] === nums[r - 1]) {
                    r--
                }
                // 去重后的指针移动
                l++
                r--
            }
        }
    }
    return res
};