【温故知新】`18. 四数之和` 由2Sum 3Sum 递归演化为 nSum: n数之和

849 阅读1分钟

两数之和

题目描述

给定一个整数数组 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]

提示:

2 <= nums.length <= 104 -109 <= nums[i] <= 109 -109 <= target <= 109 只会存在一个有效答案 进阶:你可以想出一个时间复杂度小于 O(n2) 的算法吗?

解题思路

哈希映射hasMap

  • 我们创建一个哈希表;
  • 对于每一个 x,我们首先查询哈希表中是否存在 target - x;
  • 然后将 x 插入到哈希表中,即可保证不会让 x 和自己匹配。

时空复杂度

  • 时间复杂度:O(N),其中 N 是数组中的元素数量。对于每一个元素 x,我们可以 O(1) 地寻找 target - x。

  • 空间复杂度:O(N),其中 N 是数组中的元素数量。主要为哈希表的开销。

代码

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(nums, target) {
   let map=new Map();
   // 
   for(let i=0;i<nums.length;i++){
       if(!map.has(target-nums[i])){
           map.set(nums[i],i);
       }else{
           return [i,map.get(target-nums[i])]
       }
   }
};

两数之和变种

题目描述

nums中有多对儿元素之和等于target,请你使用算法返回所有的和为target的元素对儿,其中不能重复;

var twoSum = function(nums, target)

比如nums=[1,2,2,3,3,2,1],target=4;返回的结果即为[[1,3],[2,2]]

解题思路

  • 排序+双指针实现;
    • 通过排序便于我们进行元素去重;
    • 通过left,right双指针相向移动
    • 通过while循环进行左右指针指向的相同元素去重;

时空复杂度

  • 时间复杂度O(N);while循环O(N),N为nums.length;排序时间复杂度为O(NlogN)
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(nums, target) {
   // 排序+双指针
   // 排序 
   nums.sort((a,b)=>{return a-b})
   //双指针left  right;
   let left=0;right=nums.length-1;
   let res=[];
    while(left<right){
        sum=nums[left]+nums[right]
        let leftVal=nums[left],rightVal=nums[right];
        if(sum<target){
            // 左侧去重
            while(left<right&&nums[left]==leftVal) left++;
        }else if(sum>target){
            // 右侧去重
            while(left<right&&nums[right]==rightVal) right--;
        }else{
          // 去重
            res.push([nums[left],nums[right]])
            while(left<right&&nums[left]==leftVal)left ++
            while(left<right&&nums[right]==rightVal) right--
  
        }
    }// end of while
    return res;
};

三数之和

给你一个包含 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

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/3s… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解题思路

  • 排序+双指针实现 同上
    • 通过排序便于我们进行元素去重;
    • 通过left,right双指针相向移动
    • 通过while循环进行左右指针指向的相同元素去重;
    • 关键点是需要基准值去重
    // 基准值去重;
     if(i>0&&nums[i]===nums[i-1]){
         continue
     }
    

代码

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var threeSum = function(nums) {
 // 思路 排序+双指针实现;  
 let res=[];
 // 1、排序
 nums.sort((a,b)=>{return a-b});

 // 遍历每一个元素作为基准点;
 for(let i=0;i<nums.length;i++){
     // 对于本题来说 三数之和为0;最小值大于0;一定不存在;
     if(nums[i]>0){
         return res;
     }
     //定义左右指针 left  right;
     let left =i+1,right=nums.length-1;
     // 基准值去重;
     if(i>0&&nums[i]===nums[i-1]){
         continue
     }
     while(left<right){
         let sum=nums[i]+nums[left]+nums[right]
         if(sum===0){
             // left 去重
             while(left<right&&nums[left]===nums[left+1])left ++;
             // right 去重
             while(left<right&&nums[right]===nums[right-1]) right --;
             res.push([nums[i],nums[left],nums[right]]);
             left++
             right--

         }else if(sum<0){
             left++
         }else {
             right--
         }
     }// end of while

 }// end of for 
 return  res;

};

解题思路2

  • 确定基准值以后,剩下的数字就是求两数之和 target-nums[i];利用求两数之和twoSum函数;求三数之和

代码

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var threeSum = function(nums) {
 // 另一种思路; 确定基准值以后,剩下的数字就是求两数之和 target-nums[i];利用求两数之和twoSum函数;
  return threeSumTarget(nums,0)
 
};

var threeSumTarget=function (nums,target){
       let res=[];
    nums.sort((a,b)=>{return a-b});
    // 遍历每个基准点;
    for(let i=0;i<nums.length;i++){
        // 求两数之和
        let tuples=twoSumTarget(nums,i+1,target-nums[i])
        //满足tuples 则加入nums[i],并push进res;
        console.log(tuples);
        for(let j=0;j<tuples.length;j++){
            tuples[j].push(nums[i])
            res.push(tuples[j]);
        }//
      while(i<nums.length-1&&nums[i]===nums[i+1]){
          i++
      }
    }// end of for 
    
    return res;
}

var twoSumTarget=function(nums,start,target){
    let res=[];
    let left =start,right=nums.length-1;
    while(left<right){
        let sum=nums[left]+nums[right]
        let leftVal=nums[left],rightVal=nums[right]
        if(sum<target){
            // left 去重;
            while(left<right&&nums[left]===leftVal) left++
        }else if(sum>target ){
            while(left<right&&nums[right]===rightVal) right--;
        }else {
            res.push([nums[left],nums[right]]);
            // left  right 去重;
            while(left<right&&nums[left]===leftVal) left++
            while(left<right&&nums[right]===rightVal) right--;
        }
    }// end of while 
    return res;
}

四数之和

题目描述

给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] :

0 <= a, b, c, d < n a、b、c 和 d 互不相同 nums[a] + nums[b] + nums[c] + nums[d] == target 你可以按 任意顺序 返回答案 。

 

示例 1:

输入:nums = [1,0,-1,0,-2,2], target = 0 输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]] 示例 2:

输入:nums = [2,2,2,2,2], target = 8 输出:[[2,2,2,2]]  

提示:

1 <= nums.length <= 200 -109 <= nums[i] <= 109 -109 <= target <= 109

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/4s… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解题思路

如果我们求n数之和怎么办呢?

  • 由我们之前做过的2数之和,3数之和进而递归演化为nSum:n数之和;
    • nSumTarget:n数之和求解公式
    • nSumTarget中参数nums,必须是已经排序数组;
    • 当n===2时,走两数之和twoSumTarget的双指针方法;
    • 当n>2时,穷举第一个数(注意去重);
      • 然后递归调用(n-1)Sum,组装答案;

代码

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[][]}
 */
var fourSum = function(nums, target) {
    nums.sort((a,b)=>{return a-b});
    // nsum
    return nSumTarget(nums,4,0,target);
};

/**
 * * @param {number[]} nums sorted nums
 * * @param {number[]}  n n数之和
 * * @param {number} start  下标左指针
 * @param {number} target  求和目标
 * @return {number[][]}
*/
var nSumTarget=function(nums,n,start,target){
    let size=nums.length;
    let res=[]
    if(n<2||size<n){
        return res;
    }
    // n===2;两数之和
    if(n===2){
        // 两数之和
        let left =start,right=nums.length-1;
        while(left<right){
            let sum=nums[left]+nums[right]
            let leftVal=nums[left],rightVal=nums[right]
            if(sum<target){
                // left 去重;
                while(left<right&&nums[left]===leftVal) left++
            }else if(sum>target ){
                while(left<right&&nums[right]===rightVal) right--;
            }else {
                res.push([nums[left],nums[right]]);
                // left  right 去重;
                while(left<right&&nums[left]===leftVal) left++
                while(left<right&&nums[right]===rightVal) right--;
            }
        }// end of while 
    }else{
        //n>2时,递归计算(n-1)Sum;
        for(let i=start;i<size;i++){
            // 基准点去重
            if(i>start&&nums[i]===nums[i-1]){
                continue
            }
            sub=nSumTarget(nums,n-1,i+1,target-nums[i])
            console.log(sub);
            for(item of sub){
                item.push(nums[i]);
                res.push(item);
            }
            
        }// end of for

    }
    return res;
}