考点:双指针
思路
参考官方题解,一步一步从三重循环优化。
首先三重循环的时间复杂度太高,而且最后的结果不能有重复数组,又进一步提高了空间复杂度。
其次考虑到一点:结果需要的是三个数之和为0,如果数组是有序的,当第一层循环遍历到数组元素大于0的时候可以直接结束循环,同时还可以摒弃掉一些重复元素的影响,因此第一步必然是排序。
然后考虑一下里面的两层循环:当一、二层循环固定元素 a、b 的时候,第三层循环确定了一个元素 c,当 a 或 b 增加的时候,c 必然减小,所以二层循环与三层循环是关联的,因而可以用两个指针代替二、三层循环,这样就简化了时间复杂度。此时要分三种情况:1,三数之和小于 0,则左指针自增;2,三数之和等于 0,结果加入三个数的数组,同时左右指针分别自增及自减;3,三数之和大于 0,右指针自减。注意这三种情况下都要考虑重复元素的情况,即当前元素与右边(左指针)/左边(右指针)的是否相等,如果相等就自增/自减到不等为止。
(p.s. 虽然官方题解一直被吐槽晦涩难懂,但是本题的官方题解很详细且能懂,值得细看)
遍历过程可以参考这个题解
var threeSum = function(nums) {
// 排序
nums.sort((a, b) => a - b);
let i, j; // 左右指针
const res = []; // 结果数组
for(let k = 0; k < nums.length - 2; k++) {
// 如果第一层循环的元素已经大于0了,那么后面的元素也必然大于0
// 此时没有继续循环的必要了,直接跳出
if(nums[k] > 0) break;
// 如果遇到重复元素,一直跳到最后一个
if(k > 0 && nums[k] === nums[k - 1]) continue;
// 左右指针初始化
i = k + 1;
j = nums.length - 1;
while(i < j) {
// 保存三数之和
const s = nums[i] + nums[j] + nums[k];
// 三数之和小于0
if(s < 0) {
// 左指针自增
i++;
// 跳过重复元素
while(i < j && nums[i] === nums[i - 1]) i++;
}
// 三数之和等于0
else if(s === 0) {
// 加入结果数组,同时左右指针向中间移动
res.push([nums[i++], nums[j--],nums[k]]);
// 跳过重复元素
while(i < j && nums[i] === nums[i - 1]) i++;
while(i < j && nums[j] === nums[j + 1]) j--;
}
// 三数之和大于0
else {
// 右指针自减
j--;
// 跳过重复元素
while(i < j && nums[j] === nums[j + 1]) j--;
}
}
}
return res;
};