力扣:两数之和与n数之和的(Map)与(排序+双指针)解法 【三刷终于明白HashMap求和的去重问题】

啃一本算法书啃了快一年了,用嘴想一想都该只剩渣了,脑子是怎么想的???

  • 真希望有一天“爷啃完了,爷不要你了,爷换一本啃”,,欸欸欸??罪过罪过!!!
  • 哈哈哈

一、 两数之和

1-1

  • 两数之和【返回数组下标】使用HashMap解决就很巴适,真简单!
  • 排序+双指针给n数之和铺路【返回数组元素】
    • 排序之后数组下标对应的元素发生改变!!!

1-1-1 排序+双指针【返回!!数组元素!!不是数组下标】

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(nums, target) {
    
    
    // 当需要返回结果数组时
    // 排序+双指针
    nums.sort((a,b) => {
    
    return a-b;})
    let left = 0,right = nums.length-1;
    
    const res = [];

    // 左右指针
   while(left < right) {
    
    
       let leftValue = nums[left],rightValue = nums[right];
       let sum = leftValue + rightValue
       
       if(sum < target){
    
    
           while(left < right && nums[left] == leftValue) left++;  
       } 
       else if(sum > target) {
    
    
           while (left < right && nums[right] == rightValue)  right--;
       }
       else {
    
    
           res.push(nums[left],nums[right])
           while(left < right && nums[left] == leftValue ) left++;
           while(left < right && nums[right] == rightValue ) right--;
       }
   }

   return res;
};

1-1-2 HashMap 两数之和

  • 用 hashMap 存储遍历过的元素和对应的索引。
  • 每遍历一个元素,看看 hashMap 中是否存在满足要求的目标数字。
  • 所有事情在一次遍历中完成(空间换取时间)
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(nums, target) {
    
    
   const map = new Map();
   for(let i in nums) {
    
    
    
    if(map.get(target - nums[i])) {
    
    
        return [i,map.get(target - nums[i])]
    }
    map.set(nums[i],i)
   } 
};

二、 三数之和【返回数组元素】

2-1 三数之和

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]

2-1-1 排序+双指针

在这里插入图片描述

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var threeSum = function(nums) {
    
    
    return threeSumTarget(nums, 0)
};
const threeSumTarget = (nums, target) => {
    
    
    nums.sort((a,b)=>a-b)
    // const res = new Set();
    const res= [];
    for(let i = 0; i<nums.length; i++) {
    
    
        let temp = towSumTarget(nums, i+1, target - nums[i])
        if(temp.length) {
    
    
            for(let item of temp) {
    
    
                item.push(nums[i])
                res.push(item);
            }
        }
        // 跳过第一个数字重复的情况,否则会出现重复结果
        while(i < nums.length-1 && nums[i]==nums[i+1]) i++;
    }
    return res;
}
var towSumTarget = function(nums, start, target) {
    
    

    // 当需要返回结果数组时
    // 排序+双指针
    nums.sort((a,b) => {
    
    return a-b;})
    let left = start,right = nums.length-1;
    
    const res = [];

    // 左右指针
   while(left < right) {
    
    
       let leftValue = nums[left];
       let rightValue = nums[right];
       if(nums[left] + nums[right] < target) left++;
       else if(nums[left] + nums[right] > target) right--;
       else {
    
    
           res.push([nums[left],nums[right]])
           while(left < right && nums[left] == leftValue ) left++;
           while(left < right && nums[right] == rightValue ) right--;
       }
   }

   return res;
};




2-1-2 思路分析

  1. 确定 第一个数字nums[i]之后,剩下的两个数字就是和为target-nums[i]的两个数字

    • 三数之和------》 两数之和
  2. 请添加图片描述

2-1-3 去重逻辑的思考

说道去重,其实主要考虑三个数的去重。 a, b ,c, 对应的就是 nums[i],nums[left],nums[right]

2-1-3-1 a的去重

  1. 分析

    • a 如果重复了怎么办,a是nums里遍历的元素,那么应该直接跳过去。

      • 但这里有一个问题,是判断 nums[i] 与 nums[i + 1]是否相同,还是判断 nums[i] 与 nums[i-1] 是否相同。

      • 都是和 nums[i]进行比较,是比较它的前一个,还是比较他的后一个。

        if (nums[i] == nums[i + 1]) {
                  
                   // 去重操作
            continue;
        }
        
    • 那就我们就把 三元组中出现重复元素的情况直接pass掉了。 例如{-1, -1 ,2} 这组数据,当遍历到第一个-1 的时候,判断 下一个也是-1,那这组数据就pass了。

      • 我们要做的是 不能有重复的三元组,但三元组内的元素是可以重复的!
  2. 所以这里是有两个重复的维度。那么应该这么写:

if (i > 0 && nums[i] == nums[i - 1]) {
    
    
    continue;
}
  • 这么写就是当前使用 nums[i],我们判断前一位是不是一样的元素,在看 {-1, -1 ,2} 这组数据,当遍历到 第一个 -1 的时候,只要前一位没有-1,那么 {-1, -1 ,2} 这组数据一样可以收录到 结果集里。

2-1-3-2 b与c的去重

  1. 很多同学写本题的时候,去重的逻辑多加了 对right 和left 的去重:(代码中注释部分)
while (right > left) {
    
    
    if (nums[i] + nums[left] + nums[right] > 0) {
    
    
        right--;
        // 去重 right
        while (left < right && nums[right] == nums[right + 1]) right--;
    } else if (nums[i] + nums[left] + nums[right] < 0) {
    
    
        left++;
        // 去重 left
        while (left < right && nums[left] == nums[left - 1]) left++;
    } else {
    
    
    }
}
  1. 这种去重其实对提升程序运行效率是没有帮助的。

    • 拿right去重为例,即使不加这个去重逻辑,依然根据 while (right > left) 和 if (nums[i] + nums[left] + nums[right] > 0) 去完成right-- 的操作。
    • 多加了 while (left < right && nums[right] == nums[right + 1]) right–; 这一行代码,其实就是把 需要执行的逻辑提前执行了,但并没有减少 判断的逻辑。
    • 最直白的思考过程,就是right还是一个数一个数的减下去的,所以在哪里减的都是一样的。

    • 所以这种去重 是可以不加的。 仅仅是 把去重的逻辑提前了而已。

猜你喜欢

转载自blog.csdn.net/hannah2233/article/details/128536805