题目
给你一个由 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.length <= 200
-10^9 <= nums[i] <= 10^9
-10^9 <= target <= 10^9
示例
示例 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]]
解题思路及代码
方法一:暴力搜索
(强烈不推荐!!!)
在不考虑时间代价的前提下,最简单的方式就是采用4个for循环嵌套的方式,但是这样子做的话时间复杂度将会达到可怕的,所以还是不建议大家采用这种方式。
这里我也就不给大家展示相关代码了,相信大家应该很容易实现。
方法二:排序+双指针
在做过了leetcode数组系列中的两数之和、三数之和等类似的问题之后,相信大家应该对这种双指针的方式并不陌生了。我这个专栏前面两期的内容中也都给大家介绍过了这种方式,这里我们仍然可以采用排序+双指针的方式解决这个四个数求和的问题。
与三数之和不同的是,我们之前是设置两个指针加一个nums[i]进行遍历,由于这里面涉及到了四个数字,所以我们就需要考虑设置两个指针加上nums[i]、nums[j]来进行遍历操作。
具体的操作方式可以参考我在《15.三数之和》和《16.最接近的三数之和》中的介绍。
这里给出几个值得注意的点:
- 首先开始是一个特例排除,如果数组长度小于4直接返回空。
- 要注意去重,如果在遍历时遇到连续两个数一样的情况可以直接跳过。
考虑几个剪枝操作:
(注:不进行剪枝操作不影响结果的正确性,只是会减少程序运行的时间)
- 如果从nums[i]开始连续四个数相加的和已经比target大,我们可以直接结束循环。
- 如果nums[i]和nums数组结尾的三个数相加的和已经比target小,我们可以直接结束循环。
- nums[j]与nums[i]类似。
以下是完整代码:
#include<iostream>
#include<cstdlib>
#include<vector>
#include<algorithm>
using namespace std;
// 排序+双指针
vector<vector<int>> fourSum(vector<int>& nums, int target){
vector<vector<int>> res = {};
int n = nums.size(); // nums数组长度
if(n < 4){ // 数组长度小于4,直接返回空
return res;
}
sort(nums.begin(), nums.end()); // nums排序
for(int i = 0; i < n - 3 && (long)nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] <= target; i++){
if(i > 0 && nums[i] == nums[i - 1]){ // 去重
continue;
}
if((long)nums[i] + nums[n - 1] + nums[n - 2] + nums[n - 3] < target){ // 剪枝
continue;
}
for(int j = i + 1; j < n - 2 && (long)nums[i] + nums[j] + nums[j + 1] + nums[j + 2] <= target; j++){
if(j > i + 1 && (long)nums[j] == nums[j - 1]){ // 去重
continue;
}
if((long)nums[i] + nums[j] + nums[n - 1] + nums[n - 2] < target){ // 剪枝
continue;
}
int L = j + 1;
int R = n - 1;
while (L < R){
long sum = (long)nums[i] + nums[j] + nums[L] + nums[R];
if(sum == target){ // 找到符合条件的四元组
res.push_back({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--;
}else if(sum <target){ // 和小于target,L右移
L++;
}else{ // 和大于target,R左移
R--;
}
}
}
}
return res;
}
int main(void){
vector<int> nums = {1,0,-1,0,-2,2};
int target = 0;
vector<vector<int>> res = fourSum(nums, target);
cout << "[";
for(int i = 0; i < res.size(); i++){
cout << "[";
for(int j = 0; j < res[i].size(); j++){
if(j == res[i].size() - 1){
cout << res[i][j] << "]";
}else{
cout << res[i][j] << ",";
}
}
if(i != res.size() - 1){
cout << ",";
}
}
cout << "]" << endl;
system("pause");
return 0;
}
方法三:map记录每个数字出现的次数
这个方法是我在《LeetCode Cookbook》中学习到的方法,在我之前的一篇文章《15.三数之和》中也有提到过。
大致的思路就是设置一个map提前存储每个数字出现的次数,然后就可以将原本可能带有重复数字的nums数组转化为一个不含有重复数字的uniqNums数组和一个记录数字出现次数的map。
这个时候就可以根据nums[a]、nums[b]、nums[c]、nums[d]的关系来将问题分成四种情况来讨论:
- 四个数都相同:只需要保证这个数在map中出现四次及以上即可。
- 两个不同的数:分为“3个相同的数+另一个数”和“两个相同的数+另外两个相同的数”两种情况。还是需要保证数字在map中出现的次数即可。
- 三个不同的数:两个相同的数+另两个不同的数,保证相同的那个数在map中出现两次及以上即可。
- 四个不同的数:这里面我们设置一个值diff=target-我们确定的那三个数,如果这个差值diff可以在我们的map中找到,就说明存在四个数的和等于target,这样就可以减少循环的数量,避免四重循环产生。
以下是完整代码:
#include<iostream>
#include<cstdlib>
#include<vector>
#include<algorithm>
#include<map>
using namespace std;
vector<vector<int>> fourSum(vector<int>& nums, int target){
vector<vector<int>> res = {};
map<int, int> counter; // 记录每个数字出现的次数
for(int num : nums){
counter[num]++;
}
vector<int> uniqNums; // 存储不重复的数字
for(auto& entry : counter){
uniqNums.push_back(entry.first);
}
sort(uniqNums.begin(), uniqNums.end()); // 排序
for(int i = 0; i < uniqNums.size(); i++){
if((long)uniqNums[i] * 4 == target && counter[uniqNums[i]] >= 4){ // 四个数都相同
res.push_back({uniqNums[i], uniqNums[i], uniqNums[i], uniqNums[i]});
}
for(int j = i + 1; j < uniqNums.size(); j++){ // 两个不同的数
if((long)uniqNums[i] * 3 + uniqNums[j] == target && counter[uniqNums[i]] >= 3){
res.push_back({uniqNums[i], uniqNums[i], uniqNums[i], uniqNums[j]});
}
if((long)uniqNums[j] * 3 + uniqNums[i] == target && counter[uniqNums[j]] >= 3){
res.push_back({uniqNums[i], uniqNums[j], uniqNums[j], uniqNums[j]});
}
if((long)uniqNums[i] * 2 + uniqNums[j] * 2 == target && counter[uniqNums[i]] >= 2 && counter[uniqNums[j]] >= 2){
res.push_back({uniqNums[i], uniqNums[i], uniqNums[j], uniqNums[j]});
}
for(int k = j + 1; k < uniqNums.size(); k++){ // 三个不同的数
if((long)uniqNums[i] * 2 + uniqNums[j] + uniqNums[k] == target && counter[uniqNums[i]] >= 2){
res.push_back({uniqNums[i], uniqNums[i], uniqNums[j], uniqNums[k]});
}
if((long)uniqNums[j] * 2 + uniqNums[i] + uniqNums[k] == target && counter[uniqNums[j]] >= 2){
res.push_back({uniqNums[i], uniqNums[j], uniqNums[j], uniqNums[k]});
}
if((long)uniqNums[k] * 2 + uniqNums[i] + uniqNums[j] == target && counter[uniqNums[k]] >= 2){
res.push_back({uniqNums[i], uniqNums[j], uniqNums[k], uniqNums[k]});
}
int diff = target - uniqNums[i] - uniqNums[j] - uniqNums[k];
if(diff > uniqNums[k] && counter[diff] > 0){ // 四个不同的数
res.push_back({uniqNums[i], uniqNums[j], uniqNums[k], diff});
}
}
}
}
return res;
}
int main(void){
vector<int> nums = {1,0,-1,0,-2,2};
int target = 0;
vector<vector<int>> res = fourSum(nums, target);
cout << "[";
for(int i = 0; i < res.size(); i++){
cout << "[";
for(int j = 0; j < res[i].size(); j++){
if(j == res[i].size() - 1){
cout << res[i][j] << "]";
}else{
cout << res[i][j] << ",";
}
}
if(i != res.size() - 1){
cout << ",";
}
}
cout << "]" << endl;
system("pause");
return 0;
}