一、题目
给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
注意:
答案中不可以包含重复的四元组。
示例:
给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。
满足要求的四元组集合为:
[
[-1, 0, 0, 1],
[-2, -1, 1, 2],
[-2, 0, 0, 2]
]
二、解决
1、暴力
思路: 四层循环,暴力求解,将答案放到Set中即可。
代码: 复杂度太高,必然超时,就不写了。
时间复杂度: O(
n
4
n^4
n4)
空间复杂度: O(n)
2、三层循环+单数哈希
思路: 类似【LeetCode】15. 三数之和,有兴趣可看该文章。
代码:
class Solution {
public List<List<Integer>> fourSum(int[] num, int target) {
ArrayList<List<Integer>> ans = new ArrayList<>();
if(num.length<4)return ans;
Arrays.sort(num);
for(int i=0; i<num.length-3; i++){
if(num[i]+num[i+1]+num[i+2]+num[i+3]>target)break; //first candidate too large, search finished
if(num[i]+num[num.length-1]+num[num.length-2]+num[num.length-3]<target)continue; //first candidate too small
if(i>0&&num[i]==num[i-1])continue; //prevents duplicate result in ans list
for(int j=i+1; j<num.length-2; j++){
if(num[i]+num[j]+num[j+1]+num[j+2]>target)break; //second candidate too large
if(num[i]+num[j]+num[num.length-1]+num[num.length-2]<target)continue; //second candidate too small
if(j>i+1&&num[j]==num[j-1])continue; //prevents duplicate results in ans list
int low=j+1, high=num.length-1;
while(low<high){
int sum=num[i]+num[j]+num[low]+num[high];
if(sum==target){
ans.add(Arrays.asList(num[i], num[j], num[low], num[high]));
while(low<high&&num[low]==num[low+1])low++; //skipping over duplicate on low
while(low<high&&num[high]==num[high-1])high--; //skipping over duplicate on high
low++;
high--;
}
//move window
else if(sum<target)low++;
else high--;
}
}
}
return ans;
}
}
时间复杂度: O(
n
3
n^3
n3)
空间复杂度: O(
n
n
n)
3、两层循环+两数和哈希
思路: 类似2.
代码:
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
if(nums == null || nums.length < 4) {
return new ArrayList<>(res);
}
int n = nums.length;
Arrays.sort(nums);
//key represents sum of first two nums, value is the list of indexes of those two nums
//We dont need to worry about duplicate here, we deal with that in res set
Map<Integer, List<int[]>> map = new HashMap<>();
//because res can be duplicated, we use set to deal with that
//Set of a list compare all the elements in the list defaultly, you don't need to override compare and hash function
Set<List<Integer>> res = new HashSet<List<Integer>>();
for(int i = 0; i < n; i++) {
for(int j = i + 1; j < n; j++) {
int sum = nums[i] + nums[j];
//check if current nums[i] and nums[j] can combine with previous first two nums to get a target value;
if(map.containsKey(target - sum)) {
List<int[]> indexes = map.get(target - sum);
for(int[] index : indexes) {
// Here we already know that we can get 4 nums to get the target value, but we need those four nums comes in order
//assume we have first two nums nums[k], nums[l], and current nums are nums[i], nums[j], by definition, k < l and i < j are certain. So we only need to make sure that l < i, then we can get k < l < i < j, and add it to result set
if(index[1] < i) {
//make a result
// asList(),初始化,静态,不支持增删元素
List<Integer> candidate = Arrays.asList(nums[index[0]], nums[index[1]], nums[i], nums[j]);
res.add(candidate);
}
}
}
List<int[]> temp = map.getOrDefault(sum, new ArrayList<>());
temp.add(new int[]{i, j});
map.put(sum, temp);
}
}
//convert from Set<List> to List<List>
return new ArrayList<>(res);
}
}
时间复杂度: O(
n
2
n^2
n2)
空间复杂度: O(
n
n
n)
4、回溯
思路: 暂时不太理解,后续懂了再补充吧。
代码:
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> ans = new ArrayList<List<Integer>>();
if(nums == null || nums.length == 0) return ans;
List<Integer> list = new ArrayList<>();
Arrays.sort(nums);
getSum(nums, 0, target, ans, list, 0);
return ans;
}
private void getSum(int[] nums, int sum, int target, List<List<Integer>> ans, List<Integer> list, int pos){
if(list.size() == 4 && sum == target && !ans.contains(list)){
ans.add(new ArrayList<>(list)); return;
}else if(list.size() == 4) return;
for(int i = pos; i < nums.length; i++){
if(nums[i] + nums[nums.length - 1] * (3 - list.size()) + sum < target) continue;
if(nums[i] * (4 - list.size()) + sum > target) return;
list.add(nums[i]);
getSum(nums, sum + nums[i], target, ans, list, i + 1);
list.remove(list.size() - 1);
}
}
}
时间复杂度: 略。
空间复杂度: 略。
5、递归(K数相加)
思路: 注意整数极限。
代码:
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
Arrays.sort(nums);
return kSum(nums, 0, 4, target);
}
private List<List<Integer>> kSum (int[] nums, int start, int k, int target) {
int len = nums.length;
List<List<Integer>> res = new ArrayList<List<Integer>>();
if(k == 2) { //two pointers from low and high
int low = start, high = len - 1;
while(low < high) {
int sum = nums[low] + nums[high];
if(sum == target) {
List<Integer> path = new ArrayList<Integer>();
path.add(nums[low]);
path.add(nums[high]);
res.add(path);
while(low < high && nums[low] == nums[low + 1]) low++;
while(low < high && nums[high] == nums[high - 1]) high--;
low++;
high--;
} else if (sum < target) { //move low
low++;
} else { //move high
high--;
}
}
} else {
for(int i = start; i < len - (k - 1); i++) {
if (i > start && (long)nums[i]*k > target) break;
if (i > start && (long)nums[i] + (long)nums[nums.length - 1] *(k-1) < target) continue;
if(i > start && nums[i] == nums[i - 1]) continue;
// use current number to reduce kSum into k-1 sum
List<List<Integer>> resTemp = kSum(nums, i + 1, k - 1, target - nums[i]);
for(List<Integer> t : resTemp) {
t.add(0, nums[i]);
}
res.addAll(resTemp);
}
}
return res;
}
}
时间复杂度: O(
n
k
−
1
n^{k-1}
nk−1)
空间复杂度: O(
k
n
2
kn^2
kn2)
三、参考
1、(Java) 25 lines of code, easy to understand answer that is O(n^2), based on two sum solution
2、Clean accepted java O(n^3) solution based on 3sum
3、3, 4,N数之和解法 双指针 通俗易懂
4、20-line, elegant accepted JAVA solution using Backtracking.
5、My solution generalized for kSums in JAVA
6、K数之和的通用模板
7、2sum->3sum->4sum->…->nsum
8、List add(int index, E element) method in Java
9、在 Java 中初始化 List 的五种方法