最后编辑于2025-07-01
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
思路概览
四数之和可以看作三数之和的推广,经典做法有两种:
排序 + 固定两层 + 双指针(O(n^3)):先排序,再枚举两个固定位置 i, j,剩下用左右指针在有序区间内寻找满足条件的两数对。核心在于做好去重(固定层跳过相同数字、找到解后移动指针并跳过重复)。
通用 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; for (int j = i + 1; j < n - 2; ++j) { if (j > i + 1 && nums[j] == nums[j - 1]) continue; 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; while (left < right && nums[right] == nums[right - 1]) --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,可采用递归统一实现。