描述
给出一个有n个元素的数组S,S中是否有元素a,b,c满足a+b+c=0?找出数组S中所有满足条件的三元组。
数据范围:0 \le n \le 10000≤n≤1000,数组中各个元素值满足 |val | \le 100∣val∣≤100
空间复杂度:O(n^2)O(n
2
),时间复杂度 O(n^2)O(n
2
)
注意:
三元组(a、b、c)中的元素必须按非降序排列。(即a≤b≤c)
解集中不能包含重复的三元组。
例如,给定的数组 S = {-10 0 10 20 -10 -40},解集为(-10, -10, 20),(-10, 0, 10)
分析:
根据题意求数组中任意三个数相加零的三元组。相对于寻找两数之和来说,空间复杂度可能会加一。
一, 刚开始没有思路,用笨办法实现,能想到的就是三重遍历,本题性能要求不高也是可以通的
num.sort((a, b) => a - b);
const result = [];
num.forEach((item1, index1) => {
num.forEach((item2, index2)=> {
num.forEach((item3, index3) => {
if (
index1 !== index2
&& index1 !== index3
&& index2 !== index3
&& (item1 + item2 + item3) === 0
) {
/*
追加数据
*/
}
})
})
这样判定肯定是有重复的, 先将其序列化之后用Set去重。
result.push(
JSON.stringify(
[item1, item2, item3]
.sort((a, b) => a - b)
)
);
最后再反序列化转为我们要的结果。
return [...new Set(result)].map(item => {
return JSON.parse(item);
});
第一版全部代码
/**
*
* @param num int整型一维数组
* @return int整型二维数组
*/
function threeSum( num ) {
// write code here
num.sort((a, b) => a - b);
const result = [];
num.forEach((item1, index1) => {
num.forEach((item2, index2)=> {
num.forEach((item3, index3) => {
if (
index1 !== index2
&& index1 !== index3
&& index2 !== index3
&& (item1 + item2 + item3) === 0
) {
result.push(
JSON.stringify(
[item1, item2, item3]
.sort((a, b) => a - b)
)
);
};
});
});
});
return [...new Set(result)].map(item => {
return JSON.parse(item);
});
}
module.exports = {
threeSum : threeSum
};
虽然本题这样也通过了题目的要求,但是不符合使用算法的初心。
二,根据第一版的结果,考虑使用双指针和哈希表,通过减少一层循环。根据题意三数之和的结果为零,得出,其中任意两个数的和,等于第三个数的反数。
还是先使用了哈希表的解法,根据第50题《两数之和》思路,使用哈希表先保存了所有值,同时将在数组中的位置也存了下来,利用不同索引位和三数之和等于零这个条件来判断,但是这样输出后的结果和答案的位置不一样,卡在了13题
num.sort((a, b) => a - b);
const result = [];
const negative = [];
const just = [];
const temp = {};
const zero = num.filter(item => item === 0);
if (num.length < 3) {
return [];
} else if (num.length >= 3 && !num.some(item => item > 0)) {
return [[0, 0, 0]];
}
num.forEach((item, index) => {
if (temp[item] !== undefined) {
temp[item].indexList.push(index);
} else {
temp[item] = {
item,
indexList: [index]
}
}
});
num.forEach((item1, index1)=> {
num.forEach((item2, index2) => {
const value = - (item1 + item2);
if (
typeof temp[value] === 'object'
&& (value + item1 + item2 === 0)
&& temp[value].indexList.indexOf(index1) === -1
&& temp[value].indexList.indexOf(index2) === -1
&& index1 !== index2
) {
result.push(
JSON.stringify(
[temp[value].item, item1, item2]
.sort((a, b) => a - b)
)
);
}
});
})
想要的数据是拿到了,但是和答案的位置不同判定失败,、
如果没有重复的数,应该是可以过的。
然后使用双指针解法。使用双指针提前一定是要排序的。(从左向右/从小到大)遍历,取当前遍历的值作为基准值,然后扫描当前值以后的值。一个向左一个向右。不断缩小范围。在找的过程中如果扫描的值之和取反后大于当前的值,那么需要将左指针向右移动,因为我们的数据是升序排序的,越往右越大,反之越往左越小。在找的过程中如果扫描的值之和取反后小于当前的值,那么需要将右指针向左移动。如果扫描的值之和取反不大于当前值,也不小于当前值。那么就是等于当前值。找到之后需要将左右指针同时移动一位。还需要跳过重复。需要注意的是跳过重复必须是在同时移动左右指针位置之前,否则将不达预期。数据的处理方式和之前一样不变。然后就可以通过了。
/**
*
* @param num int整型一维数组
* @return int整型二维数组
*/
function threeSum( num ) {
// write code here
num.sort((a, b) => a - b);
const result = [];
num.forEach((item, index) => {
let left = index + 1;
let right = num.length - 1;
while(left < right) {
if (item < -(num[left] + num[right])) {
left += 1;
}else if (item > -(num[left] + num[right])) {
right -= 1;
} else {
result.push(
JSON.stringify([item, num[left], num[right]])
)
while (num[left] === num[left + 1]) {
left += 1;
}
while (num[right] === num[right - 1]) {
right -= 1;
}
right -= 1;
left += 1;
}
}
});
return [...new Set(result)].map(item => {
return JSON.parse(item);
});
}
module.exports = {
threeSum : threeSum
};
总结:虽然这题过了,但是感觉还没解到点子上。第50题是两数之和,这一题是三数之和,那如果是五数之和,十数之和呢,总不能用五个指针十个指针,最好的方法应是,先找到有多少种组合,计算每种组合相加的结果得到我们想要的答案。