思路
1.题目描述
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 。请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例1:
示例2:
2.题目分析
2.1 思想的起源——穷举
三元组的计算我们很容易就可以想到利用三层循环来进行解题,但是很明显O(N3)的时间复杂度是不可行的,所以我们考虑对其进行优化。虽然起初模型是穷举法,但是也有一些可以做的工作,首先我们要计算的是三个值nums[i],nums[j]以及nums[k]
的和,而且不能重复,不能重复的话我们可以首先对数组排个序,排序之后的nums
在计算和时也会更加方便,有序就会有规律可循,就可以更好的优化算法。首先写出穷举法的伪代码如下:
sort(nums)
for i = 0...n-1:
if i== 0 or nums[i] != nums[i-1] then
for j = i+1..n-1
if j==i+1 or nums[j] != nums[j-1] then
for k = j+1...n-1
if k==j+1 or nums[k] != nums[k-1] then
if nums[i]+nums[k]+nums[k]=0 then
add({nums[i],nums[j],nums[k]}) into ret
解题时要注意去重!
2.1 穷举的优化——双指针
- 我们可以观察上面的穷举法伪代码,可以看到里面三个数的和的时候我们采用了三层循环,其中第一重循环是待求三元组的起始点,第二第三层循环分别从不小于上一层循环的元素开始枚举,那么我们就发现一个规律:
**第一层循环元素i<第二层循环元素j<第三层循环元素k**
。 - 我们利用这个规律就可以进行循环的降重了。如果对于两重循环i和j,总是有i<j成立,其实i与j可以处于同一层分别从两个方向进行循环。所以对于这一类的循环可以利用双方向的枚举从而将其中两层循环变成并列关系,也就是说我们可以从小到大枚举 j,同时从大到小枚举 k,即第二重循环和第三重循环就变成并列的关系。
那么我们就可以写出如下的过程:
- 先将数组排序;
i
指向当前迭代的第一个数,left
指向第i+1
个数,right
指向最后一个数, 它们组成的元组为(nums[i],nums[left], nums[right]
);- 当
nums[i] > 0
时, 由于nums
已升序排列, 因此nums[i]+nums[left]+nums[right]
必然大于0, 因此没有符合条件的元组,程序结束; - 当
nums[i]+nums[left]+nums[right] > 0
时, 说明此时的right指向的值已经不满足要求,所以向左移动right以获得更小的nums[right]; - 当
nums[i]+nums[left]+nums[right] < 0
时, 说明此时的left指向的值太小已经不满足要求,所以向右移动left以获得更大的nums[left]; - 当
nums[i]+nums[left]+nums[right] == 0
时, 将元组插入待返回vector。由于nums升序, 且在这之前对i做了去重处理,所以此时 该元组组合必定是当前已有组合中唯一的,。 - 然后对left右侧和right左侧进行去重;
3.题目解答
1)正确解法:
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(), nums.end());
for (int i = 0; i < nums.size(); i++) {
if (nums[i] > 0) {
return result;
}
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
int left = i + 1;
int right = nums.size() - 1;
while (left < right) {
if (nums[i] + nums[left] + nums[right] > 0) {
right--;
} else if (nums[i] + nums[left] + nums[right] < 0) {
left++;
} else {
result.push_back(vector<int>{nums[i], nums[left], nums[right]});
while (left < right && nums[right] == nums[right - 1]) {
right--;
}
while (left < right && nums[left] == nums[left + 1]) {
left++;
}
right--;
left++;
}
}
}
return result;
}
};
2)官方解法
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
int n = nums.size();
sort(nums.begin(), nums.end());
vector<vector<int>> ans;
// 枚举 a
for (int first = 0; first < n; ++first) {
// 需要和上一次枚举的数不相同
if (first > 0 && nums[first] == nums[first - 1]) {
continue;
}
// c 对应的指针初始指向数组的最右端
int third = n - 1;
int target = -nums[first];
// 枚举 b
for (int second = first + 1; second < n; ++second) {
// 需要和上一次枚举的数不相同
if (second > first + 1 && nums[second] == nums[second - 1]) {
continue;
}
// 需要保证 b 的指针在 c 的指针的左侧
while (second < third && nums[second] + nums[third] > target) {
--third;
}
// 如果指针重合,随着 b 后续的增加
// 就不会有满足 a+b+c=0 并且 b<c 的 c 了,可以退出循环
if (second == third) {
break;
}
if (nums[second] + nums[third] == target) {
ans.push_back({nums[first], nums[second], nums[third]});
}
}
}
return ans;
}
};