# [18] 四数之和
给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):
0 <= a, b, c, d < n
a、b、c 和 d 互不相同
nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。
示例 1:
输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
示例 2:
输入:nums = [2,2,2,2,2], target = 8
输出:[[2,2,2,2]]
提示:
1 <= nums.length <= 200
-10^9 <= nums[i] <= 10^9
-10^9 <= target <= 10^9
作为 [1] 两数之和 与 [15] 三数之和 的再一次升级版,这一次我们可以整理出这一类问题的通用套路了。
实际上 nSum 问题的通用解法为:先通过遍历数组选定 N 元组中最小的那个数,之后再通过遍历选定第二小的数……当然这一过程可以用递归实现。
直到剩余 2 个数未确定,此时调用 2Sum 方法,通过空间换时间,将 O(n^2) 的时间复杂度缩小为 O(n)。
在进入下一级递归之前,我们需要做好一定的剪枝来提升性能。例如,最小的 4 个数都小于 target,直接退出;或者最大的 4 个数都大于 target, 直接跳过。再例如,当发现当前值与下一个值相同时,说明有重复元素,同样跳过。
最终,nSum 的时间复杂度为 O(n^(N-1))。所以随着 N 的增大,这一算法的优越度也变得越来越低了……因为 N=5 以上以后,一个 O(n^4) 的算法已经足以让很多用例直接超时了。
因此,掌握常规算法,并了解其衍生的题型改造就已经足够了,出 5Sum,6Sum 的题并无必要。
function fourSum(nums: number[], target: number): number[][] {
nums = nums.sort((a, b) => a - b);
const len = nums.length;
const res: number[][] = [];
for (let i = 0; i < len - 3; i++) {
// 最小的 4 个数都小于 target,直接退出;或者最大的 4 个数都大于 target, 直接跳过
if (nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target) break;
if (nums[i] + nums[len - 1] + nums[len - 2] + nums[len - 3] < target) continue;
// 去掉重复情况
if (i > 0 && nums[i - 1] === nums[i]) continue;
// 接下来就是 3Sum 问题了
threeSumCase(i, nums[i], target, nums, res);
}
return res;
function threeSumCase(start: number, first: number, target: number, nums: number[], res: number[][]) {
const len = nums.length;
for (let i = start + 1; i < len - 2; i++) {
const second = nums[i];
// 最小的 4 个数都小于 target,直接退出;或者最大的 4 个数都大于 target, 直接跳过
if (first + second + nums[i + 1] + nums[i + 2] > target) break;
if (first + second + nums[len - 1] + nums[len - 2] < target) continue;
// 去掉重复情况
if (i > start + 1 && nums[i - 1] === nums[i]) continue;
// 接下来就是 2Sum 问题了
let left = i + 1;
let right = nums.length - 1;
while (left < right) {
const sum = first + second + nums[left] + nums[right];
if (sum === target) {
res.push([first, second, nums[left], nums[right]]);
// 去除重复情况
while (left < right && nums[left + 1] === nums[left]) left += 1;
while (left < right && nums[right - 1] === nums[right]) right -= 1;
// 指针移动到下一组情况
left += 1;
right -= 1;
} else if (sum > target) {
right -= 1;
} else {
left += 1;
}
}
}
}
}