题目描述:
内容分析:
- 返回是是个数组
- 答案不唯一,多对元素之和等于target
- 元素对不重复:也就是说[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;
};
结果要去重,而去重的成本太高,所以我们直接换个思路
排序+双指针:
最终解法:
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= 当前数+两数之和。
- 所以先遍历数组
for(let i=0; i<nums.length;i++){
}
- 然后再分解任务,求出两数之和的元素数组(这个下文👇🏻有)
let tuples = twoSum(nums,i+1, target-nums[i]);
- 再push当前值,再push到最终的数组集合中
for(let item of tuples){
item.push(nums[i]);
res.push(item);
}
- 进行过滤,跳过第一个数重复的情况。比如[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]))