1 题目描述
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: The solution set must not contain duplicate quadruplets.
难度:Medium
2 题目样例
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]
]
3 题意分析
给定数组,寻找所有和为target的组合,也就是说[1,2,3,4]和[4,2,3,1]是同一个组合不应该在答案中出现两次。
4 思路分析
1 暴力枚举
先对数组排序,接着假设 ,然后分别遍历 ,在剩下的数组中二分查找 ,同时利用std::set来判重。
(似乎直接四重循环更加暴力?)
代码如下
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
int len = nums.size();
set<vector<int>> s;
vector<vector<int>> ans;
sort(nums.begin(), nums.end());
for(int i=0;i<len-3;i++)
for(int j=i+1;j<len-2;j++)
for(int k=j+1;k<len-1;k++)
if(binary_search(&nums[k+1], &nums[len], target - nums[i] - nums[j] - nums[k]))
s.insert({nums[i], nums[j], nums[k], target - nums[i] - nums[j] - nums[k]});
for(auto &v:s)
ans.emplace_back(v);
return ans;
}
};
排序的复杂度为 ,std::set::insert()的复杂度是 ,所以时间复杂度是 ,空间复杂度不太好分析,但应该有 ?此处存疑。
我觉得我仿佛找到了Nsum的通解!(逃
2 优化查找
还记得在3Sum的解法吗?
没错我们依旧可以参考3Sum的做法,先对数组排序,接着假设 ,但是只遍历 ,然后在剩下的数组空间里寻找所有满足条件的 和 。
具体说来就是,假设 且 ,我们不断移动 和 ,然后令 ,在保证的 的情况下作如下循环
- 如果 ,说明这时满足题意,把 加入答案,同时 和 分别向右和向左移动到非重复元素为止。
- 如果 说明 大了,需要把 向左移动。
- 如果 说明 小了,需要把 向右移动。
具体代码如下
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
int len = nums.size();
sort(nums.begin(),nums.end());
vector<vector<int>> ans;
int tp;
int l;
int r;
for(int i=0;i<len-3;i++){
while(i<len-3&&i>0&&nums[i]==nums[i-1])
i++;
for(int j=i+1;j<len-2;j++){
while(j<len-2&&j>i+1&&nums[j]==nums[j-1])
j++;
l = j + 1;
r = len -1;
while(l<r){
tp = nums[i] + nums[j] + nums[l] + nums[r] - target;
if(tp==0){
ans.push_back({nums[i], nums[j], nums[l], nums[r]});
while(l<r&&nums[l]==nums[l+1])
l++;
l++;
while(l<r&&nums[r]==nums[r-1])
r--;
r--;
}
else if(tp > 0)
r--;
else
l++;
}
}
}
return ans;
}
};
这里有一个细节就是在每次循环对i和j的处理,保证了相同的元素只会被处理一次,防止出现重复组合。
整体时间复杂度 ,空间复杂度依旧存疑。
3 进一步优化
刚才思路2中的代码实际上还有很多情况可以优化,比如
- 如果 ,那么显然应该直接跳出。
- 如果 ,那么显然应该直接遍历下一个元素。
实际上这些优化在3Sum的过程中也可以完成的,这里我直接粘讨论区的代码了
public List<List<Integer>> fourSum(int[] nums, int target) {
ArrayList<List<Integer>> res = new ArrayList<List<Integer>>();
int len = nums.length;
if (nums == null || len < 4)
return res;
Arrays.sort(nums);
int max = nums[len - 1];
if (4 * nums[0] > target || 4 * max < target)
return res;
int i, z;
for (i = 0; i < len; i++) {
z = nums[i];
if (i > 0 && z == nums[i - 1])// avoid duplicate continue;
if (z + 3 * max < target) // z is too small continue;
if (4 * z > target) // z is too large break;
if (4 * z == target) { // z is the boundary if (i + 3 < len && nums[i + 3] == z)
res.add(Arrays.asList(z, z, z, z));
break;
}
threeSumForFourSum(nums, target - z, i + 1, len - 1, res, z);
}
return res;
}
/* * Find all possible distinguished three numbers adding up to the target * in sorted array nums[] between indices low and high. If there are, * add all of them into the ArrayList fourSumList, using * fourSumList.add(Arrays.asList(z1, the three numbers)) */
public void threeSumForFourSum(int[] nums, int target, int low, int high, ArrayList<List<Integer>> fourSumList,
int z1) {
if (low + 1 >= high)
return;
int max = nums[high];
if (3 * nums[low] > target || 3 * max < target)
return;
int i, z;
for (i = low; i < high - 1; i++) {
z = nums[i];
if (i > low && z == nums[i - 1]) // avoid duplicate continue;
if (z + 2 * max < target) // z is too small continue;
if (3 * z > target) // z is too large break;
if (3 * z == target) { // z is the boundary if (i + 1 < high && nums[i + 2] == z)
fourSumList.add(Arrays.asList(z1, z, z, z));
break;
}
twoSumForFourSum(nums, target - z, i + 1, high, fourSumList, z1, z);
}
}
/* * Find all possible distinguished two numbers adding up to the target * in sorted array nums[] between indices low and high. If there are, * add all of them into the ArrayList fourSumList, using * fourSumList.add(Arrays.asList(z1, z2, the two numbers)) */
public void twoSumForFourSum(int[] nums, int target, int low, int high, ArrayList<List<Integer>> fourSumList,
int z1, int z2) {
if (low >= high)
return;
if (2 * nums[low] > target || 2 * nums[high] < target)
return;
int i = low, j = high, sum, x;
while (i < j) {
sum = nums[i] + nums[j];
if (sum == target) {
fourSumList.add(Arrays.asList(z1, z2, nums[i], nums[j]));
x = nums[i];
while (++i < j && x == nums[i]) // avoid duplicate ;
x = nums[j];
while (i < --j && x == nums[j]) // avoid duplicate ;
}
if (sum < target)
i++;
if (sum > target)
j--;
}
return;
}
复杂度相对思路2来说没有变化,但是这些剪枝有效降低了代码运行时间。
5 后记
从2Sum到NSum,的确是一系列相当不错的题呢。
不知道对于NSum有没有更好的优化方法,现在主要还是依赖查询线性优化来让NSum的时间复杂度降低到 。