力扣15:三数之和

154 阅读5分钟

题目描述:

image.png

内容分析:

  1. 返回是是个数组
  2. 答案不唯一,多对元素之和等于target
  3. 元素对不重复:也就是说[1,2,3]和[3,2,1]和[3,1,2]和[1,3,2]是一样的

解题思路:

依然显示使用暴力枚举法,但是这里发现暴力枚举法无法得出正确答案,因为题目要求不要重复结果

暴力枚举(❌)

var threeSum = function(nums) {
    let res = [];
    for(let i=0;i<nums.length-2;i++){
        for(let j=i+1;j<nums.length-1;j++){
            for(let k=j+1;k<nums.length;k++){
                if(nums[i]+nums[j]+nums[k]===0){
                res.push([nums[i],nums[j],nums[k]]) 
                }
            }
        }
    }
    return res;
};

image.png

结果要去重,而去重的成本太高,所以我们直接换个思路

排序+双指针:

最终解法:

var threeSum = function(nums) {
    nums.sort((a,b)=>a-b);
    return threeSumTarget(nums,0);
};
var threeSumTarget = function(nums,target) {
    let res = [];
    for(let i=0; i<nums.length;i++){
        let tuples = twoSum(nums,i+1, target-nums[i]);
        for(let item of tuples){
            item.push(nums[i]);
            res.push(item);
        }
        while(i<nums.length-1 && nums[i]===nums[i+1])i++;
    }
    return res;
}

var twoSum = function(nums,start,target){
   // 传入的就是排好序的数组
    let left = start; 
    let right = nums.length-1;
    let res = [];
    while(left<right){
        let tempLeft = nums[left];
        let tempRight = nums[right];
        let sum = tempLeft+tempRight;
        if(sum===target){
            res.push([ nums[left], nums[right] ]);
            // 如果相同应该跳过
            while(left<right && nums[left]==tempLeft) left++;
            while(left<right && nums[right]==tempRight) right--;
        }else if(sum>target){
            while(left<right && nums[right]==tempRight) right--;
        }else if(sum<target){
            while(left<right && nums[left]==tempLeft) left++;
        }
    }
    return res;
}

下面来介绍一下思路:

1. 排序
nums.sort((a,b)=>a-b);
2. 如何求解三数之和?

那么可以先求解两数之和,target= 当前数+两数之和

  1. 所以先遍历数组
for(let i=0; i<nums.length;i++){
   
}
  1. 然后再分解任务,求出两数之和的元素数组(这个下文👇🏻有)
 let tuples = twoSum(nums,i+1, target-nums[i]);
  1. 再push当前值,再push到最终的数组集合中
for(let item of tuples){
    item.push(nums[i]);
    res.push(item);
}
  1. 进行过滤,跳过第一个数重复的情况。比如[0,0,0]这种情况

因为i的值=0的时候已经求出了正确值[0,0,0],
所以如果i+1的值也是0的话,
那么i+1层的遍历求出来的也是[0,0,0],就重复了。
所以这种情况下就while跳过,直到不同的值出现。
i<数组长度-1是为了防止边界溢出

while(i<nums.length-1 && nums[i]===nums[i+1])i++;

这里为什么不前置判断if(nums[i]===nums[i+1])continue;?
因为如果选择重复元素的最后一个做搜索的话, 会导致结果的缺失。所以这里应该使用重复元素的第一个元素,而不是最后一个。所以过滤的操作就放在了函数体的最后面!可以用测试数据[-1,0,1,2,-1,-4]试一下

3. 分解的任务:用双指针相对而行找到有序数组的两数之和
var twoSum = function(nums,start,target){
   // 传入的就是排好序的数组
    let left = start;  
    let right = nums.length-1;
    let res = [];
    while(left<right){
        let tempLeft = nums[left];
        let tempRight = nums[right];
        let sum = tempLeft+tempRight;
        if(sum===target){
            res.push([ nums[left], nums[right] ]);
        }else if(sum>target){
            right--;
        }else if(sum<target){
            left++;
        }
    }
    return res;
}

那么这个代码的结果就是会有重复解

比如[-2,0,0,2,2] 就会产生重复解[[0,2],[0,2]]。 所以就需要去重!循环遍历跳过所有重复元素!

大家可以观察下,他和最终版代码的就一个区别,while(left<right && nums[left]==tempLeft)就是这样的一句话,这句话的意思是什么呢? 其实就是说,如果你当前的值==你存储下来的那个值,那你就移动指针,直到他们不一样。这样就可以跳过所有重复的元素。

也可以是另一种写法,但这个比较是比较left和left+1的值,指针最后指向的是重复元素的最后一个,所以完成while循环后,需要再移动一位!!!!

while(left<right && nums[left]==nums[left+1]){
    left++;
}
left++;

小作业:四数之和?

var fourSum = function(nums, target) {
    nums.sort((a,b)=>a-b);
    return fourSumTarget(nums,0,target);
};
// 四数之和
var fourSumTarget = function(nums, start, target) {
    let res = [];
    for(let i=start; i<nums.length;i++){
        let tuples = threeSumTarget(nums,i+1, target-nums[i]);
       
        for(let item of tuples){
            item.push(nums[i]);
            res.push(item);
        }
        while(i<nums.length-1 && nums[i]===nums[i+1])i++;
    }
    return res;
}
// 三数之和
var threeSumTarget = function(nums, start, target) {
    let res = [];
    for(let i=start; i<nums.length;i++){
        let tuples = twoSum(nums,i+1, target-nums[i]);
       
        for(let item of tuples){
            item.push(nums[i]);
            res.push(item);
        }
        while(i<nums.length-1 && nums[i]===nums[i+1])i++;
    }
    return res;
}
//两数之和
var twoSum = function(nums,start,target){
   // 传入的就是排好序的数组
    let left = start; 
    let right = nums.length-1;
    let res = [];
    while(left<right){
        let tempLeft = nums[left];
        let tempRight = nums[right];
        let sum = tempLeft+tempRight;
        if(sum===target){
            res.push([ nums[left], nums[right] ]);
            // 如果相同应该跳过
            while(left<right && nums[left]==tempLeft) left++;
            while(left<right && nums[right]==tempRight) right--;
        }else if(sum>target){
            while(left<right && nums[right]==tempRight) right--;
        }else if(sum<target){
            while(left<right && nums[left]==tempLeft) left++;
        }
    }
    return res;
}

100数之和: 递归遍历

var nSum = function(nums) {
    nums.sort((a,b)=>a-b);
    return nSumTarget(nums,0,8,4);
};
// n 是求几数之和
var nSumTarget = function(nums,start,target,n) {
    let res = [];
    if(n<2||nums.length<2) return res;
    if(n==2){
        res = twoSum(nums,start,target)
    }else{
        // 多数之和
        for(let i=start; i<nums.length;i++){
            let tuples = nSumTarget(nums,i+1, target-nums[i], n-1);
            for(let item of tuples){
                item.push(nums[i]);
                res.push(item);
            }
            while(i<nums.length-1 && nums[i]===nums[i+1])i++;
        }
    }

    return res;
}
// 2数之和的函数
var twoSum = function(nums,start,target){
    // 传入的就是排好序的数组
     let left = start; 
     let right = nums.length-1;
     let res=[];
     while(left<right){
         let tempLeft = nums[left];
         let tempRight = nums[right];
         let sum = tempLeft+tempRight;
         if(sum===target){
             res.push([ nums[left], nums[right] ]);
             // 如果相同应该跳过
             while(left<right && nums[left]==tempLeft) left++;
             while(left<right && nums[right]==tempRight) right--;
         }else if(sum>target){
             while(left<right && nums[right]==tempRight) right--;
         }else if(sum<target){
             while(left<right && nums[left]==tempLeft) left++;
         }
     }
     return res;
 }

console.log("结果:",nSum([1,1,1,2,3,4,5,3,7,8,0]))