Leetcode15 史上最详细题解之三数之和 | 刷题打卡

449 阅读1分钟

题目描述

给你一个包含 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 = []
输出:[]

示例3.

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

提示:

0 <= nums.length <= 3000
-105 <= nums[i] <= 105

解题思路

看到这题最简单的就是暴力解法,直接三重for循环就完事了。但是这样的空间复杂度和时间复杂度都很高,那么有没有什么办法能够优化了?这时候我就想到了用双指针(对撞指针)的方法。

如果喜欢看代码,可直接跳过文字,代码也有注释。

  • 首先,根据示例可知,如果数组为空或者数组长度不足3,则直接返回原数组;
  • 那么先对给定数组nums进行排序,这里的话就用sort()升序排序(倒序也行,不过后面代码就是变了,不过思路是一样的)
  • 题目既然是要求三数之和,那么我们就定义三个数出来:第一个数为 nums[i],i 从 0 开始遍历数组;第二个数为nums[left],即左指针为 left , 第三个数为nums[right],即右指针为 right。
  • 题目又说三数之和要为 0 ,那么我们就定义一个空数组res,如果满足条件就把满足条件的三数插入到数组中;
  • 因为三个数中第一个数是最小的,所以先说第一个数,如果nums[i] 大于0 ,那么直接break结束循环,因为如果三个数中的第一个数都比0大,那么后面的数肯定都比 0 大,所以直接结束循环;
  • 反之如果nums[i] 小于0,就让left = i + 1,right = nums.length - 1;
  • 当左指针比右指针小的时候进行while循环, 判断三数之和是否满足条件;
  • 如果满足条件就以数组的形式放进空数组中,并且对左指针和右指针做去重的操作,如果指针本次的值和上一次的值相同,那么就跳过本次循环,这样就避免结果出现重复数组;
  • 如果三数之和不满足条件,即三数之和小于0,左指针右移;三数之和大于0,右指针左移;
  • 最后第一个数循环结束,返回 res 数组即为数组中所有满足条件的集合。

AC代码

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var threeSum = function(nums) {
  let res = [] // 定义一个空数组
  if(nums == null || nums.length < 3) return res // 如果数组为空或者数组元素不足3个,则返回原数组
  nums.sort((a,b) => a - b) // 对给定数组进行升序排序,如果想降序排序只需把 a - b 改成 b - a即可
  for(let i = 0; i < nums.length; i++) { // 第一重循环,给定第一个值
      if(nums[i] > 0) break // 如果第一个值都大于0,说明后面的值都比0大,所以直接结束循环
      if(nums[i] == nums[i-1]) continue // 如果当前值和前一个值相同,则结束本次循环并进入下次循环,这样做的目的是保证ans中不会出现重复的数组
      let l = i + 1 // 第二个值,也可以说是左指针
      let r = nums.length - 1 // 第三个值,即右指针
      while(l < r) { // 只有当左指针小于右指针才进行循环;如果左指针大于或等于右指针的话,说明从 i 之后的所有数都已经遍历完了,本次循环已经结束
          let sum = nums[i] + nums[l] + nums[r] // 定义三数之和
          if(sum == 0) { // 如果三数之和满足条件
              res.push([nums[i],nums[l],nums[r]]) // 就将这三个数以数组的形式 push 到 ans 数组中
              while(l < r && nums[l] == nums[l+1]){ // 去重,如果不去重的话就可能会出现重复的数组被 push 到 ans 数组中
                  l++
              }
              while(l < r && nums[r] == nums[r-1]){ // 去重,同上
                  r--
              }
              l++ // 左指针右移
              r-- // 右指针左移
          } else if(sum < 0){ // 小于 0 说明左指针的值小了,所以左指针右移,因为排序之后是升序数组
              l++
          } else if(sum > 0) { // 大于 0 说明右指针的值大了,所以右指针向左移 
              r--
          }
      }
      
  }

  return res

};

总结

如果这份题解能够帮助到您的话,希望动动各位发财的金手指点个赞哟。在对撞指针的基础上还可以再进行优化,就比如去重操作那里可以用hashMap,欢迎各位大佬在评论区评论。

本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情