四数之和
给你一个由 n 个整数组成的数组 nums,和一个目标值 target
请你找出和为target的所有四元组组合
注:四元组中的数对应nums中的索引不能重复,且四元组组合也不能重复
0 <= a, b, c, d < n
a、b、c 和 d 互不相同
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]]
暴力法
暴力法枚举所有情况,并将每种组合的数hash去重
- Javascript
/**
* @param {number[]} nums
* @param {number} target
* @return {number[][]}
*/
var fourSum = function (nums, target) {
const result = [];
const n = nums.length;
let sum;
const set = new Set();
function hash() {
return [...arguments]
.sort((a, b) => a - b)
.join(',');
}
for (let i = 0; i < n; i++) {
for (let j = i + 1; j < n; j++) {
for (let k = j + 1; k < n; k++) {
for (let l = k + 1; l < n; l++) {
sum = nums[i] + nums[j] + nums[k] + nums[l];
if (sum !== target) continue;
const hashValue = hash(nums[i], nums[j], nums[k], nums[l]);
if (set.has(hashValue)) continue;
result.push([nums[i], nums[j], nums[k], nums[l]]);
set.add(hashValue);
}
}
}
}
return result;
};
- cpp
class Solution {
public:
string hash(vector<int> nums){
string s;
for(int val:nums){
s+=to_string(val);
s+=',';
}
return s;
}
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> result;
int n = nums.size();
int sum;
set<string> s;
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
for (int k = j + 1; k < n; k++) {
for (int l = k + 1; l < n; l++) {
sum = nums[i] + nums[j] + nums[k] + nums[l];
if (sum != target) continue;
vector<int> t={nums[i],nums[j],nums[k],nums[l]};
sort(t.begin(),t.end());
string hashValue = hash(t);
if (s.find(hashValue)!=s.end()) continue;
result.push_back(t);
s.insert(hashValue);
}
}
}
}
return result;
}
};
最终我们 js 代码的提交不负众望的过了287个用例,只差两个
时间复杂度:O(n^4) 空间复杂度:O(n^4)
解法一:排序+双指针
之前我们已解决过三数之和的问题,在这里我们将问题简化一下,复用下三数之和的解法
将四数之和变成 固定其中一个数 与三数之和 等于 target 的问题
用 [1,0,-1,0,2,-2],target=0 举例
-
固定 1,剩下求 [0,-1,0,2,-1] 中三数之和为 -1 的组合
-
固定 0,剩下求 [1,-1,0,2,-2] 中三数之和为 0 的组合
- JavaScript
/**
* @param {number[]} nums
* @param {number} target
* @return {number[][]}
*/
var fourSum = function (nums, target) {
const result = [];
const n = nums.length;
nums = nums.sort((a, b) => a - b);
let sum;
let l, r;
for (let i = 0; i < n; i++) {
if (i > 0 && nums[i] === nums[i - 1]) continue;
for (let j = i + 1; j < n; j++) {
if (j > i + 1 && nums[j] === nums[j - 1]) continue;
l = j + 1;
r = n - 1;
while (l < r) {
sum = nums[i] + nums[j] + nums[l] + nums[r];
if (sum < target) {
l++;
} else if (sum > target) {
r--;
} else {
result.push([nums[i], nums[j], nums[l], nums[r]]);
l++;
r--;
// 去重
while (l < r && nums[l] === nums[l - 1]) {
l++;
}
// 去重
while (r >= 0 && nums[r] === nums[r + 1]) {
r--;
}
}
}
}
}
return result;
};
- C++
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> result;
int n = nums.size();
sort(nums.begin(),nums.end());
long sum;
int l,r;
for (int i = 0; i < n; i++) {
// 去重
if(i>0 && nums[i]==nums[i-1])continue;
for (int j = i + 1; j < n; j++) {
// 去重
if(j>i+1 && nums[j]==nums[j-1])continue;
l=j+1,r=n-1;
while(l<r){
sum = (long)nums[i] + (long)nums[j] + (long)nums[l] + (long)nums[r];
if(sum<target){
l++;
}else if(sum>target){
r--;
}else{
vector<int> t={nums[i],nums[j],nums[l],nums[r]};
result.push_back(t);
l++;
r--;
// 去重
while(l<r&&nums[l]==nums[l-1]){
l++;
}
// 去重
while(r>=0&&nums[r]==nums[r+1]){
r--;
}
}
}
}
}
return result;
}
};
时间复杂度:O(n^3)
空间复杂度:取决于排序算法
解法二:DFS + 剪枝
我们直接使用深度优先搜索枚举所有四数组合的话,在数据集达到一定量就会超时,因此我们需要通过剪枝优化算法的时间复杂度
先看一下深度优先搜索的过程
我们的树中,从上往下的路径便是枚举出来的组合,例如:[-2,-1,0,0] [-2,0,1,2]
数据集需要先排序,便于剪枝
用 [-2,-1,0,0,1,2] target=0 举例子,看看剪枝过程
- 去重剪枝
青色节点组合跟黄色节点组合是重复的,因此青色节点组合应该要剪掉
并且观察到是数组中重复元素导致的,所以在代码里可以通过判断重复节点来跳过搜索
- 短路径剪枝
黄色节点的路径都不满足 4 个元素,因此要剪掉
- 最大值剪枝
数组是有序的,我们可以通过数组最后一项得知我们能组合相加的最大值,来判断往后继续搜索的必要性
例如 target 如果为 10 的时候,我们用例可能组合成的最大值是 8 ,因此整颗数都被剪了
- 最小值剪枝
target 为 -4 的时候,从黄色节点开始,能组合成的最小值是 加不到 -4,因此要剪掉
最小值剪枝后,如下图,我们的 dfs 时间复杂度大大降低
上代码:
- JavaScript
/**
* @param {number[]} nums
* @param {number} target
* @return {number[][]}
*/
var fourSum = function(nums, target) {
const t=[]
const len=nums.length
const result=[]
// 排序
nums=nums.sort((a,b)=> a-b)
function dfs(idx,n,target){
if(!n){
// 四数和=target,添加结果
if(!target){
result.push([...t])
}
return
}
// 短路径剪枝
if(len-idx<n){
return
}
// 最小值剪枝
if(target<n*nums[idx]){
return
}
// 最大值剪枝
if(target>n*nums[len-1]){
return
}
for(let i=idx;i<len;i++){
// 去重剪枝
if(i>idx && nums[i]===nums[i-1])continue
t.push(nums[i])
dfs(i+1,n-1,target-nums[i])
t.pop()
}
}
dfs(0,4,target)
return result
};
- C++
class Solution {
public:
void dfs(int idx,int n,int target,vector<vector<int>> &result,vector<int> &path,vector<int> &nums){
if(!n){
// 结果添加
if(!target){
vector<int> t(path);
result.push_back(t);
}
return;
}
// 短路径剪枝
if(nums.size()-idx<n){
return;
}
// 最大值剪枝
if(target>(long)nums[nums.size()-1]*n){
return;
}
// 最小值剪枝
if(target<(long)nums[idx]*n){
return;
}
for(int i=idx;i<nums.size();i++){
// 去重剪枝
if(i>idx && nums[i]==nums[i-1])continue;
path.push_back(nums[i]);
dfs(i+1,n-1,target-nums[i],result,path,nums);
path.pop_back();
}
}
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> result;
vector<int> path;
sort(nums.begin(),nums.end());
dfs(0,4,target,result,path,nums);
return result;
}
};