力扣-18. 四数之和(4Sum)

最后编辑于2025-07-01

18. 四数之和(4Sum)

Medium

给定整数数组 nums 和目标值 target,找出所有不重复的四元组 [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. 排序 + 固定两层 + 双指针(O(n^3)):先排序,再枚举两个固定位置 i, j,剩下用左右指针在有序区间内寻找满足条件的两数对。核心在于做好去重(固定层跳过相同数字、找到解后移动指针并跳过重复)。

  2. 通用 KSum(递归):为便于推广到 5Sum、6Sum 等,采用递归方式实现 kSum。递归基线为 k==2 时使用双指针;否则固定一个数并递归求 k-1Sum。该方法逻辑统一,便于扩展。

对于去重,可以选择手动在循环与指针移动中跳过重复元素,也可借助语言库(如 C++ 的 set<vector<int>>)去重,但手动去重常数因子更小且更普遍。


方法一:使用集合去重(简单直观)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
set<vector<int>> res;
sort(nums.begin(), nums.end());
int n = nums.size();
for (int i = 0; i < n - 3; ++i) {
for (int j = i + 1; j < n - 2; ++j) {
int left = j + 1, right = n - 1;
while (left < right) {
long sum = (long)nums[i] + nums[j] + nums[left] + nums[right];
if (sum == target) {
res.insert({nums[i], nums[j], nums[left], nums[right]});
++left; --right;
} else if (sum < target) ++left;
else --right;
}
}
}
return vector<vector<int>>(res.begin(), res.end());
}
};
  • 说明: 代码简单,利用集合自动去重,但常数较大,且在某些语言中没有现成的数据结构时不可用。

方法二:排序 + 手动去重(推荐)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> res;
int n = nums.size();
if (n < 4) return res;
sort(nums.begin(), nums.end());
for (int i = 0; i < n - 3; ++i) {
if (i > 0 && nums[i] == nums[i - 1]) continue; // 去重 i
for (int j = i + 1; j < n - 2; ++j) {
if (j > i + 1 && nums[j] == nums[j - 1]) continue; // 去重 j
int left = j + 1, right = n - 1;
while (left < right) {
long sum = (long)nums[i] + nums[j] + nums[left] + nums[right];
if (sum == target) {
res.push_back({nums[i], nums[j], nums[left], nums[right]});
while (left < right && nums[left] == nums[left + 1]) ++left; // 去重 left
while (left < right && nums[right] == nums[right - 1]) --right; // 去重 right
++left; --right;
} else if (sum < target) ++left;
else --right;
}
}
}
return res;
}
};
  • 时间复杂度: O(n^3)
  • 空间复杂度: O(1)(不计答案空间)

方法三:通用 KSum(递归)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> res;
sort(nums.begin(), nums.end());
kSum(nums, target, 4, res);
return res;
}

void kSum(vector<int>& nums, int target, int k, vector<vector<int>>& res) {
vector<int> cur;
dfs(nums, (long)target, k, 0, nums.size() - 1, cur, res);
}

void dfs(vector<int>& nums, long target, int k, int left, int right, vector<int>& cur, vector<vector<int>>& res) {
if (k == 2) {
while (left < right) {
long sum = (long)nums[left] + nums[right];
if (sum == target) {
cur.push_back(nums[left]);
cur.push_back(nums[right]);
res.push_back(cur);
cur.pop_back(); cur.pop_back();
while (left < right && nums[left] == nums[left + 1]) ++left;
while (left < right && nums[right] == nums[right - 1]) --right;
++left; --right;
} else if (sum < target) ++left;
else --right;
}
return;
}
for (int i = left; i <= right; ++i) {
if (i > left && nums[i] == nums[i - 1]) continue; // 去重
cur.push_back(nums[i]);
dfs(nums, target - nums[i], k - 1, i + 1, right, cur, res);
cur.pop_back();
}
}
};
  • 说明: 该方法可推广到任意 k,但在常数与递归开销上相对较大。

作者想说的话:
四数之和本质上是固定若干项后使用双指针扫描的思路。实现时重点在于排序、范围限制与去重(在固定层与双指针移动时跳过相同元素),若想扩展到 kSum,可采用递归统一实现。