题目链接:https://leetcode-cn.com/problems/4sum/
题目描述:
给定一个包含 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
类似于三数之和的问题,先对数组排序,在固定前两个数nums[i]和nums[j]的情况下,使用两个指针,来寻找另外两个数。
同时,在固定nums[i]时,通过当前可得到的最大值和最小值来和目标值进行比较,如果最小值都比目标值大,说明对所有的nums[i]都不可能会有满足条件的,直接结束i的循环;如果最大值比目标值小,说明对当前nums[i]不可能满足条件了,进入下一个循环。
同理,固定nums[j]时也是同样的处理。
接下来的处理,和三数之和相同。
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> ans = new ArrayList<>();
int len = nums.length;
if(nums == null ||len < 4) {
return ans;
}
Arrays.sort(nums);
for(int i=0;i<len-3;i++) {
//去重,如果当前nums[i]和它前一个数相等,则跳过
if(i>0 && nums[i] == nums[i-1]) continue;
/*获取当前最小值,如果最小值比目标值大,说明后面越来越大的值不需要考虑,停止循环*/
int min=nums[i]+nums[i+1]+nums[i+2]+nums[i+3];
if(min>target){
break;
}
/*获取当前最大值,如果最大值比目标值小,说明后面越来越小的值不需要考虑,进入下一个循环*/
int max=nums[i]+nums[len-1]+nums[len-2]+nums[len-3];
if(max<target){
continue;
}
//第二个循环,固定第二个数
for(int j=i+1;j<len-2;j++) {
//去重
if(j>i+1 && nums[j] == nums[j-1]) {
continue;
}
//两个指针
int left = j+1;
int right = len-1;
/*获取当前最小值,如果最小值比目标值大,说明后面越来越大的值不许考虑,直接结束循环*/
min=nums[i]+nums[j]+nums[j+1]+nums[j+2];
if(min>target){
break;
}
/*获取当前最大值,如果最大值比目标值小,说明后面越来越小不需考虑,跳出此次循环*/
max=nums[i]+nums[j]+nums[len-2]+nums[len-1];
if(max<target){
continue;
}
//使用两个指针,left和right,left从j+1开始,right从len-1开始
//当left<right时,
//nums[i],nums[j],nums[left]和nums[right]的和为sum
//如果sum>target,就将right前移,right--
//如果sum<target,就将left后移,left++
//sum等于target,先把这一组结果记录下来
//将left++,right--
//在left<right的前提下,如果nums[left]和它的前一个数nums[left-1]相等,就将left后移,left++
//在left<right的前提下,如果nums[right]和它的后一个数nums[right+1]相等,就将right前移,right--
while(left<right){
int sum = nums[i]+nums[j]+nums[left]+nums[right];
if(sum == target){
ans.add(Arrays.asList(nums[i],nums[j],nums[left],nums[right]));
left++;
right--;
while(left<right && nums[left] == nums[left-1]) {
left++;
}
while(left<right && nums[right] == nums[right+1]) {
right--;
}
}
else if(sum<target) {
left++;
}
else right--;
}
}
}
return ans;
}
}
解答二
通过递归实现kSum。
将问题分解为: k等于2时,求两数之和;k>2时,求k-1个数之和。
class Solution {
int len=0;
public List<List<Integer>> fourSum(int[] nums, int target) {
len=nums.length;
//使用双指针数组先排序
Arrays.sort(nums);
//调用kSum
return kSum(nums,target,4,0);
}
private List<List<Integer>> kSum(int[] nums,int target,int k,int index) {
List<List<Integer>> res = new ArrayList<>();
//index表示开始的下标
if(index>=len) {
return res;
}
//递归的出口,就是求两数之和
//双指针求两数之和
if(k == 2) {
int left = index,right = len-1;
while(left<right) {
if(nums[left] + nums[right] == target) {
List<Integer> temp = new ArrayList<>();
temp.add(nums[left]);
temp.add(nums[right]);
res.add(temp);
left++;
right--;
while(left<right && nums[left] == nums[left-1]){
left++;
}
while(left<right && nums[right] == nums[right+1]){
right--;
}
}
else if(nums[left]+nums[right]>target) {
right--;
}
else left++;
}
}
else {
for(int i=index;i<len-1;i++) {
//当nums[i]重复时,直接跳过此次循环
if(i>index && nums[i] ==nums[i-1]) continue;
//当k>2时,递归调用kSum,通过递归和循环固定第一、二……的数,将target=target-第一、二……个数
List<List<Integer>> temp = kSum(nums,target-nums[i],k-1,i+1);
//当temp不为空时,说明找到和为target
if(temp != null) {
//对于得到的temp中的每一个list,都将其对应nums[i]加入在0位置上
for(List<Integer> t :temp) {
t.add(0,nums[i]);
//将得到的每一个t加入res
res.add(t);
}
}
}
}
return res;
}
}