三数之和

题目描述

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。

注意: 答案中不可以包含重复的三元组。

示例:

给定数组 nums = [-1, 0, 1, 2, -1, -4],

满足要求的三元组集合为: [ [-1, 0, 1], [-1, -1, 2] ]

思路解析

我想到了下面几个思路:

  1. 首先固定一个数,其余情况就是两数之和,时间复杂度O()
  2. 三指针,大小指针相向移动,中间用二分搜索找出对应的值,如果能实现那么时间复杂度是O(nlogn)
  3. 三指针,固定一个数,另外两个左右指针相向移动,时间复杂度O()

第一种方法的难点在于去重逻辑的实现,据参考来源所说这种方法非常麻烦。
我最开始考虑的是第二种方法,但是仔细想来这种nlogn的方法是不可能实现的,因为如果根据大小指针相加的情况来移动指针的话,被移动掉的那个数的很多组合还不能被排除掉!所以这种方法会忽略掉很多种情况。

所以考虑采用第三种方法,但是为什么第三种方法的原理能够成立呢?因为内层循环能够穷尽所有两数之和,对于已经排过序的数组来说,采用大小指针的方式不断收缩,能够准确地搜索出每一个对应的两数之和组合。我们只需要在外层循环下套一个while(left < right)的条件循环即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
for (int i = 0 ; i < nums.size();i++){
// 第一个剪枝操作,如果第一个元素已经大于0,就不可能再有和为0的了
if (nums[i] > 0) break;
// 第二个剪枝操作,如果此i对应元素等于上一个i对应元素,那么直接跳过。
if (i > 0 && nums[i] == nums[i-1]) continue;
int left = i + 1;
int right = int(nums.size()) - 1;
while (left < right){
int comp = nums[i] + nums[left] + nums[right];
if (comp < 0){
left++;
}else if (comp > 0){
right--;
}else{
ans.push_back(vector<int>{nums[i], nums[left], nums[right]});
// 第三个剪枝操作,去重
while (left < right && nums[left]==nums[left+1]) left++;
while (right > left && nums[right]==nums[right - 1]) right--;
left++;right--;
}
}
}

注意代码中三个剪枝操作。这里解释一下后两个。

第二个剪枝操作关注的是如何在i移动的过程中去重,判断的是i位置的值是不是和上一个一样,因为如果和紧挨着的上一个值一样,那么可以构造的组合肯定已经包含在上一个值所处理的组合中了。注意这里我们不能判断当前值是否等于下一个值,因为当前值若与下一个值一样,还可以构造出一个额外的组合 - - 当前值加上下一个值再加上一个数等于0,所以我们需要这样的代码。

1
if (i > 0 && nums[i] == nums[i-1])   continue;

对于第三个去重方式,我们关注的是如何在left和right移动的过程中不加入重复的组合。对于一个已经找到的组合,我们需要移动left指针直至它指向一个更大的值,移动right指针直至它指向一个更小的值,但是还需要在这个过程中注意left < right的条件,如果不加上这个条件的话就会出现下标访问超界的报错。(例子:[0,0,0,0])

1
2
while (left<right && nums[left]==nums[left+1])    left++;
while (right>left && nums[right]==nums[right-1]) right--;

经过这个操作,left和right其实还是指向的是与找到的组合相同的值,我们还需要将其相向移动一次,然后要么是不重复的情况,要么就left > right从而退出循环,进而将i加1

经过这两个去重操作,就无须构造set来去重了,就可以保证每一次加入到ans中的都是之前没有出现过的组合。

四数之和

题目描述

给定一个包含 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] ]

思路解析

这个题只需要在三数之和问题外再套一层循环即可,但还有一些细节的逻辑需要注意。在三数之和中,当遍历指针指向0的时候就退出循环,但是在这个题中不能这样剪枝,因为我们是判断和为target,不是固定的0.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
vector<vector<int>> fourSum(vector<int>& nums, int target) {
sort(nums.begin(), nums.end());
vector<vector<int>> ans;
for (int i = 0; i < int(nums.size() - 3); i++){
// 去重1
if (i > 0 && nums[i] == nums[i-1]) continue;
// 固定i,进行三数之和的查找
for (int j = i + 1; j < (nums.size() - 2); j++){
// 去重2
if (j > (i+1) && nums[j] == nums[j-1]) continue;
int left = j + 1;
int right = int(nums.size()) - 1;
while(left < right){
long comp = nums[j] + nums[left];
comp += nums[right];
if (comp < (target - nums[i])){
left++;
}else if (comp > (target - nums[i])){
right--;
}else{
ans.push_back(vector<int>{nums[i],nums[j],nums[left],nums[right]});
// 去重3
while (left < right && nums[left]==nums[left+1]) left++;
while (left < right && nums[right]==nums[right-1]) right--;
left++;
right--;
}
}
}
}
return ans;
}

注意代码中的三个去重操作,和三数之和中的思想是类似的。

来源