题目:
Given an array S of n integers, are there elements a, b, c, and d in S such that a + b + c + d = target? Find all unique quadruplets in the array which gives the sum of target.
Note:
- Elements in a quadruplet (a,b,c,d) must be in non-descending order. (ie, a ≤ b ≤ c ≤ d)
- The solution set must not contain duplicate quadruplets.
For example, given array S = {1 0 -1 0 -2 2}, and target = 0. A solution set is: (-1, 0, 0, 1) (-2, -1, 1, 2) (-2, 0, 0, 2)
题目大意:
给定一个
数组,找出所有四个数字的组合,使它们的和为target。
思路:
LeetCode上的题还真是执着,从2Sum到3Sum到3SumClosest再到4Sum。。。一路下来已经被各种Sum所折服。
这道题的解决思路是在3Sum的基础上进行一步扩展,加一层循环,使之能够处理4个数相加的情况。具体是:
1、排序
2、用第一层循环i从0开始选择第一个数,计算还剩多少到target,记录在sum1中
3、用第二层循环j每次从i + 1开始选择第二个数字,并且计算出还剩下多少才能到达target,记录在sum2中
4、第三层循环k和m(k从j + 1从前向后迭代,m从size-1开始从后向前迭代),当到达这层循环内时已经一共选择了四个数字(nums[i],nums[j],nums[k],nums[m]),因为选择的时候是有序选择的,所以四个数字是有序的。
5、判断最后选择的nums[k]和nums[m]的和sum3是否等于剩下的数,即sum2是否等于sum3,如果相等则表明这四个数字符合要求,则加入结果数组。如果sum3 < sum2表明k的值选择小了,所以应该增大k的值,即++k。如果sum3 > sum2则表明m的值选择大了,所以应该--m。
6、去重还是采用3Sum中的方法,当上一次扫描的值和本次扫描值相同时则表示本次扫描结果和上次扫描结果重复,所以这里就可以直接跳过去,即++i或++j或++k或--m。
代码:
class Solution {
public:
std::vector<std::vector<int>> fourSum(std::vector<int>& nums, int target) {
std::vector<std::vector<int>> result;
const int size = nums.size();
int i = 0, j, k, m;
if (size < 4) {
return result;
}
sort(nums.begin(), nums.end());
while (i < m - 2) {
j = i + 1;
int sum1 = target - nums[i]; //选定一个之后还剩的大小
while (j < m -1) {
int sum2 = sum1 - nums[j]; //选定两个之后还剩的大小
k = j + 1;
m = size - 1;
while (k < m) {
int sum3 = nums[k] + nums[m]; //后两个之和
if (sum2 == sum3) { //如果前两个之和和后两个之和相加之后和target相等
++k;
--m;
result.push_back({nums[i], nums[j], nums[k], nums[m]});
//如果上一次扫描的结果和本次扫描的结果相同则直接跳过
while (k < m && nums[m] == nums[m + 1]) {
--m;
}
while (k < m && nums[k] == nums[k - 1]) {
++k;
}
} else if (sum3 < sum2) {
++k;
} else {
--m;
}
}
++j;
while(j < m - 1 && nums[j] == nums[j - 1]) {
++j;
}
}
++i;
while(i < m - 2 && nums[i] == nums[i - 1]) {
++i;
}
}
return result;
}
};
优化:
根据3SumClosest一题中对于边界条件的判断在这里是可以作为优化使用的,即:
1、如果j的下两个数字相加都大于所要求的值说明以后的测试都会大于要求的值,所以本次循环已经没有什么意义了,应该改变i的值才有可能再次获得期望值。
2、如果最大的两个数字相加都小于要求值则说明j已经取小了,所以应该移动j之后再继续循环。
优化后:
class Solution {
public:
std::vector<std::vector<int>> fourSum(std::vector<int>& nums, int target) {
std::vector<std::vector<int>> result;
const int size = nums.size();
int i = 0, j, k, m;
if (size < 4) {
return result;
}
sort(nums.begin(), nums.end());
while (i < m - 2) {
j = i + 1;
int sum1 = target - nums[i]; //选定一个之后还剩的大小
while (j < m -1) {
int sum2 = sum1 - nums[j]; //选定两个之后还剩的大小
k = j + 1;
m = size - 1;
//如果j的下两个数字相加都大于所要求的值说明以后的测试都会大于要求的值
//所以本次循环已经没有什么意义了,应该改变i的值才有可能再次获得期望值
if (nums[j + 1] + nums[j + 2] > sum2) {
break;
}
//如果最大的两个数字相加都小于要求值则说明j已经取小了,所以应该移动j之后再继续循环
if (nums[size - 1] + nums[size - 2] < sum2) {
++j;
continue;
}
while (k < m) {
int sum3 = nums[k] + nums[m]; //后两个之和
if (sum2 == sum3) { //如果前两个之和和后两个之和相加之后和target相等
++k;
--m;
result.push_back({nums[i], nums[j], nums[k], nums[m]});
//如果上一次扫描的结果和本次扫描的结果相同则直接跳过
while (k < m && nums[m] == nums[m + 1]) {
--m;
}
while (k < m && nums[k] == nums[k - 1]) {
++k;
}
} else if (sum3 < sum2) {
++k;
} else {
--m;
}
}
++j;
while(j < m - 1 && nums[j] == nums[j - 1]) {
++j;
}
}
++i;
while(i < m - 2 && nums[i] == nums[i - 1]) {
++i;
}
}
return result;
}
};