前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一天一大 leet(三数之和)难度:中等 DAY-12

一天一大 leet(三数之和)难度:中等 DAY-12

作者头像
前端小书童
发布2020-09-24 11:20:59
4010
发布2020-09-24 11:20:59
举报
文章被收录于专栏:前端小书童

题目(难度:中等):

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

注意

答案中不可以包含重复的三元组

示例

代码语言:javascript
复制
给定数组 nums = [-1, 0, 1, 2, -1, -4],

满足要求的三元组集合为:
[
  [-1, 0, 1],
  [-1, -1, 2]
]

抛砖引玉

特殊情况排除

  1. 数组的长度小于 3
  2. 数组的最后一项小于 0(排序之后)

第一次循环得到的结果作为第一个数,当第一个数

当第一个数大于 0,则说明之后不会有与他组合满足条件的数了

第二个数从第一个之后依次向后查找

第三个数从最后一个数依次向前查找

当第二个数的指针大于最后一个数的指针时终止循环

当数组中存在重复的数组时,按照上面的逻辑会有重复的答案出现

初始化一个 map 去满足条件的任何两个数组合作为 map 的 key 来去重

a+b => b+a a+b

当判断筛选出来的两个数组合的 key 在 map 中出现过则说明该组合已经放到结果数组中了.

代码语言:javascript
复制
/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var threeSum = function (nums) {
  let end = nums.length,
    _result = []
  nums.sort((a, b) => a - b)
  if (nums.length < 3 || nums[end - 1] < 0) return []
  let secondMap = new Map()
  nums.some((first, i) => {
    if (first > 0) return true
    let second = i + 1
    let last = end - 1
    while (second < last) {
      if (first + nums[second] > 0) break
      let sum = first + nums[second] + nums[last]
      if (
        sum === 0 &&
        !(
          secondMap.has(nums[second] + '-' + nums[last]) ||
          secondMap.has(nums[last] + '-' + nums[second])
        )
      ) {
        _result.push([first, nums[second], nums[last]])
        secondMap.set(nums[second] + '-' + nums[last], second)
      }
      if (sum <= 0) {
        second++
      } else {
        last--
      }
    }
  })
  return _result
}

官方答案

排序 + 双指针

代码语言:javascript
复制
/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var threeSum = function (nums) {
  let sortArr = [],
    _result = [],
    n = nums.length
  nums.sort((a, b) => a - b)

  // 枚举 a
  for (let first = 0; first < n; ++first) {
    // 需要和上一次枚举的数不相同
    if (first > 0 && nums[first] == nums[first - 1]) {
      continue
    }
    // c 对应的指针初始指向数组的最右端
    let third = n - 1
    let target = -nums[first]
    // 枚举 b
    for (let second = first + 1; second < n; ++second) {
      // 需要和上一次枚举的数不相同
      if (second > first + 1 && nums[second] == nums[second - 1]) {
        continue
      }
      // 需要保证 b 的指针在 c 的指针的左侧
      while (second < third && nums[second] + nums[third] > target) {
        --third
      }
      // 如果指针重合,随着 b 后续的增加
      // 就不会有满足 a+b+c=0 并且 b<c 的 c 了,可以退出循环
      if (second == third) {
        break
      }
      if (nums[second] + nums[third] == target) {
        _result.push([nums[first], nums[second], nums[third]])
      }
    }
  }
  return _result
}

高手在民间

  • 数组从小到大排序
  • 固定值从最小到最大循环
  • start 和 end 在固定值右侧移动寻找 start + end = -(固定值)
  • 如果三数和小于 0,说明 start 对应值太小,应该右移,反之 end 左移
  • 如果三数和等于零就记录下来, L 右移,注意如果 L 的后一个和当前值相等就需要跳过,参考[0,0,0,0]
  • L 不能超过 R
  • 此时 固定值 i 右移,注意后一个 i 和当前 i 应该不相等,相等需要跳过,参考[-4,-1,-1,0,1,2]的-1
代码语言:javascript
复制
const res = []
let numsLen = nums.length
if (numsLen < 3) return []

nums.sort((a, b) => a - b)
let lastNum = null
for (let i = 0; i < numsLen; i++) {
  if (nums[i] > 0) break
  if (nums[i] === lastNum) continue

  let L = i + 1
  let R = numsLen - 1
  while (L < R) {
    const sum = nums[i] + nums[L] + nums[R]
    if (sum === 0) {
      res.push([nums[i], nums[L], nums[R]])
      while (nums[L] === nums[L + 1]) L++
      while (nums[R] === nums[R - 1]) R--
      L++
      R--
    } else if (sum > 0) {
      R--
    } else {
      L++
    }
  }
  lastNum = nums[i]
}
return res
  • 外层循环主指针 i 遍历数组。内层循环,双指针去寻找满足三数和等于 0 的项
  • 因为不能有重复的解,为了简化操作,我们先对数组预排序,则我们判断一个元素是否重复,只需看它和它之前位置的元素是否相等即可

双指针的移动时,避免出现重复解

  • 得到一个解后,需要左右指针向“内”移动,为了避免指向重复的元素
  • 左指针要在 left < right 的前提下,一直向右移动到不重复的元素上
  • 右指针要在 left < right 的前提下,一直向左移动到不重复的元素上
  • 经过了排序,如果外层遍历的数已经大于 0,则另外两个数一定大于 0,sum 不会等于 0,所以这种情况直接 break 结束遍历
代码语言:javascript
复制
/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var threeSum = function (nums) {
  nums.sort((a, b) => a - b)
  const res = []
  for (let i = 0; i < nums.length - 2; i++) {
    let n1 = nums[i]
    if (n1 > 0) break
    if (n1 == nums[i - 1] && i > 0) continue // 遍历到重复的数,跳过
    let left = i + 1
    let right = nums.length - 1
    while (left < right) {
      let n2 = nums[left]
      let n3 = nums[right]
      if (n1 + n2 + n3 === 0) {
        res.push([n1, n2, n3])
        while (left < right && nums[left] === n2) left++ //直到指向不一样的数
        while (left < right && nums[right] === n3) right--
      } else if (n1 + n2 + n3 < 0) {
        left++
      } else {
        right--
      }
    }
  }
  return res
}

菜鸡的自白

看了别人的方法就会发现,循环的逻辑基本差不多的,

首先找到一个基准

再对有序的数组,有节制的循环

不同的地方(也是我可以优化的地方)主要是对有序查询第二个和第三个数据重复的问题

声明 map 记录重复不仅增加了内存的占用,也在 map 存储和查询时增加了时间成本

优化:

第二个数后面一个数增加后与上一个相同则默认重复

最后一个数也类似

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-06-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端小书童 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 注意
  • 示例
  • 抛砖引玉
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档