[二分] 剑指 Offer II 007. 数组中和为 0 的三个数

77 阅读1分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第25天,点击查看活动详情

每日刷题 2022.08.23

题目

  • 给定一个包含 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
  • -10^5 <= nums[i] <= 10^5

解题思路

  • 看到题目的第一瞬间,就联想到了两数之和那道题。那么对于本题也是一样的,确定三个数中的两个数,就可以使用二分来查找第三个数。再次查看题目描述,只需要找到不重复的三个数,不要求三个数的顺序,那么就可以先将数组排序,之后再使用二分来查找第三个数。
  • 通过两个指针i和j来确定当前需要查找的两个数,那么第三个数等于idx = 0 - nums[i] - nums[j],之后通过二分查找在数组中查找这个数。
    • 二分查找的区间范围:j ~ n,为什么不是从0 ~ n呢?因为需要避免记录重复的三个数组。举例:i = -1, j = 0, idx = 1i = -1, idx = 0, j = 1是一样的,算作重复的,只记录一个即可。
  • 后续发现还是存在重复存储的情况,例如:-1,-1,2,0,0,1这个数组,那么-1,0,1会被记录两遍。如何处理呢?可以分析三个数,**当其中的两个数确定不重复后,那么第三个数也是一定不会重复的。**因此当遇到i或者j重复的时候,需要直接跳过,只记录一次就可以了。
  • 换一个方向来想,比如数组中的-1,-1作为i的时候是相等的,那么针对第一个-1,其往后可以组成三个数的情况,一定是包含了后面的-1往后查找的情况的,所以就无需遍历两次,避免重复。

AC代码

var threeSum = function(nums) {
  nums.sort((a, b) => {
    return a - b;
  });
  let ans = [];
  let n = nums.length, q = [];
  for(let i = 0; i < n; i++) {
    if(i != 0 && nums[i - 1] === nums[i]) continue;
    for(let j = i + 1; j < n; j++) {
      if(j != (i + 1) && nums[j - 1] === nums[j]) continue;
      // 还需要跳过相等的元素
      let cur = 0 - (nums[i] + nums[j]);
      let idx = binary(cur, j);
      if(idx != j && idx != n && nums[idx] === cur) {
        // 找到了
        let res = [nums[i], nums[j], nums[idx]];
        // 三个全部存在,说明之前计算过了
        // 不存在idx并且不和前面的两个数相等的时候
        if(idx === i || idx === j) break;
        ans.push(res);
      }
    }
  }
  return ans;
  function binary (shu, area) {
    let l = area, r = n;
    while(l + 1 != r) {
      let mid = Math.floor((l + r) / 2);
      if(nums[mid] < shu) {
        l = mid;
      }else {
        r = mid;
      }
    }
    return r;
  }
  // [-4,-1,-1,0,1,2] c 
};