15. 三数之和
核心的思路是遍历一轮数组,下标为i,然后使用双指针法,l从i的右侧出发,r从数组最右侧出发,根据此时三数之和,移动l和r。本题的关键问题在于去除重复的结果。
先对数组排序,遍历数组时当 nums[i] === nums[i - 1]时跳过此轮循环,这种方式常常用于避免计算重复的结果。同理,在双指针移动时也可能遇到重复结果。
比如说[-4, -1, -1, 0, 1, 2],i=1时,l=3,r=4,-1+0+1=0;紧接着i=2时,如果不做去重,还会有[-1, 0, 1]。
/**
* @param {number[]} nums
* @return {number[][]}
*/
var threeSum = function (nums) {
const ans = [];
const len = nums.length;
if (len < 3) return ans;
nums.sort((a, b) => a - b);
for (let i = 0; i < len - 2; i++) {
// 本题的target=0,如果最左边的数>0,直接剪枝
if (nums[i] > 0) return ans;
// 去重
if (i > 0 && nums[i] === nums[i - 1]) continue;
let l = i + 1,
r = len - 1;
while (l < r) {
const sum = nums[i] + nums[l] + nums[r];
if (sum > 0) r--;
else if (sum < 0) l++;
else {
ans.push([nums[i], nums[l], nums[r]]);
// 去重
while (l < r && nums[l] === nums[l + 1]) l++;
while (l < r && nums[r] === nums[r - 1]) r--;
l++, r--;
}
}
}
return ans;
};
18. 四数之和
基本上和三数之和一样,就是需要多一层循环去枚举j,而且还需要考虑j的去重问题。
比如 [-2, -1, -1, -1, 0, 1, 2], target = -2。
如果i=0,j=1,l=4,r=5,是可以的。但是j=2也可以,这样就会造成重复,所以还是要用上面提到的方式去重,即nums[j] = nums[j - 1]就continue。
还需要注意,j-1可能等于i,此时不应该continue,比如[2, 2, 2, 2],target = 8,显然四个数都用到了,不能因为i=0,j=1,且nums[i]=nums[j]=2,就让j=3。
/**
* @param {number[]} nums
* @param {number} target
* @return {number[][]}
*/
var fourSum = function (nums, target) {
const len = nums.length;
const ans = [];
if (len < 4) return ans;
nums.sort((a, b) => a - b);
for (let i = 0; i < len - 3; i++) {
if (nums[i] > target / 4) return ans;
if (i > 0 && nums[i] === nums[i - 1]) continue;
for (let j = i + 1; j < len - 2; j++) {
if (j !== i + 1 && nums[j] === nums[j - 1]) continue;
let l = j + 1,
r = len - 1;
while (l < r) {
const sum = nums[i] + nums[j] + nums[l] + nums[r];
if (sum > target) r--;
else if (sum < target) l++;
else {
ans.push([nums[i], nums[j], nums[l], nums[r]]);
while (l < r && nums[l] === nums[l + 1]) l++;
while (l < r && nums[r] === nums[r - 1]) r--;
l++, r--;
}
}
}
}
return ans;
};