【算法】梦开始的地方twoSum...threeSum

522 阅读3分钟

叫“梦开始的地方”,或许是因为这是大多数人LeetCode的第一站,也可能是最后一站。

有人相爱,有人夜里开车看海,有人leetcode第一题都做不出来。

2sum:两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那两个整数, 并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。 你可以按任意顺序返回答案。

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1]

示例 2:

输入:nums = [3,2,4], target = 6
输出:[1,2]

示例 3:

输入:nums = [3,3], target = 6
输出:[0,1]

第一次的想法很简单也很直白,双for循环直接遍历求解。

var twoSum = function (nums, target) {
  /* 暴力解:先在数组中固定一个一个数字,再依次判断数组中其余的数字与它的和是不是等于k,
  这种暴力解法的时间复杂度为O(n^2)
   */
  let res = [];
  for (let i = 0; i < nums.length; i++) {
    if (nums[i] > target) continue;
    let mid = target - nums[i];

    for (let j = 1; j < nums.length; j++) {
      if (nums[j] === mid && i !== j) {
        res = [i, j].sort((a, b) => a - b);
      }
    }
  }
  return res;
};

第二次考虑到优化解法的方法使用了双指针,解法。双指针适用前提是一个有序目标集合。只要选定好双端指针和指针移动边界规则就能解出双指针的解法。如图

双指针.png

// 双指针解法 O(n)
var twoSum = function (nums, target) {
  let left = 0,
    right = nums.length - 1;
  let sum = nums[left] + nums[right]; // 初始化
  while (left < right && sum != target) {
    if (sum < target) {
      // 指针移动
      left++;
    } else {
      right--;
    }
    sum = nums[left] + nums[right];
  }
  return [left, right];
};

第三种也是目前相对快的解法,通过hashmap空间换时间的方式进行快速解。相对来说,hashmap解法通常需要O(n)的空间复杂度。

twosumhashmap.png


var twoSum = function (nums, target) {
  const map = new Map(); // 数组中同一元素不重复出现

  for (let i = 0, len = nums.length; i < len; i++) {
    if (map.has(target - nums[i])) {
      let res = map.get(target - nums[i]);

      return [i, res];
    }
    map.set(nums[i], i);
  }
};

3sum:三数之和

一题二写,三数之和,题解四瞅五瞄六瞧,水平还七上八下九流,十分辣鸡。

十推九敲,八种思路,用光七情六欲五感,在这里四覆三翻二挠,一拳爆屏。

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

三数和的解法要考虑的问题相对两数之和就会复杂一些,不然怎么会有开头的对联... 实际上三数和的解法首先从对 nums 的排序开始,通过双指针解法如图, 固定一个小于0的k值,然后求出一个nums[k] + nums[l] + nums[r] = 0等式,同时避免相同三元组即可求解。

企业微信截图_20220426124859.png

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var threeSum = function(nums) {
    if(nums.length < 2) {
        return []
    }
    // nums 排序
    nums.sort((a, b) => a - b);
    let res = [];
    
    for(let k = 0 ; k < nums.length - 2; k++) { // 限定 k 的移动,同时为 l r 指针留空位
        if(nums[k] > 0) break; // nums[k] 如果大于零,那么  r > l > k 的前提下,不可能出现三数和为零的解
        if(k > 0 && nums[k] === nums[k - 1]) continue;// 跳过同值的 nums[k]
        let l = k + 1;
        let r = nums.length - 1;
        while(l < r) { // 双指针循环
            let midSum = nums[k] + nums[l] + nums[r];
            if(midSum < 0) {
                l ++;//左指针右移
                while(l < r && nums[l] === nums[l - 1]){
                    l ++
                }
            } else if( midSum > 0) {
                r --;// 右指针左移
                while(l < r && nums[r] === nums[r + 1]) {
                    r --;
                }
            } else {
                // 这里是 midSum == 0 但是要去重 避免出现重复三元组
                res.push([nums[k], nums[l], nums[r]]);
                l ++;
                r --;
                // left 指针右移后与上一位还是一样就再移一步
                while(l < r && nums[l] === nums[l - 1] ){
                    l ++;
                }
                // right 同 left
                while(l < r && nums[r] === nums[r + 1]) {
                    r --;
                }
            }
        }        
    }

    return res;
};