注意:答案中不可以包含重复的三元组,这个条件很重要。
暴力法
暴力法,对每个元素,都遍历它后面的所有元素,由于需要寻找三个数,所以需要三层循环,复杂度必然过高,而且还要考虑非重复的问题。
不过三个元素a, b, c相加:
a
+
b
+
c
=
0
a+b+c=0
a+b+c=0,其实就是
a
=
−
(
b
+
c
)
a = -(b+c)
a=−(b+c),也即是在元素a的后面寻找两个元素,使得两元素之和为
−
a
-a
−a,这么看就有点像两数之和的问题了。
LeetCode第 1 题:两数之和(C++)
实测这题虽然思路比较直接,但是写起来细节很多,很容易出错,先贴一个很暴力的解法,执行效率很低(但是能通过),因为每次循环都会去遍历当前元素之后的所有元素并构建全新的map:
class Solution {
public:
//start为检索起点
void twosum(vector<int> &nums, vector<vector<int>> &res, int start, int target){
unordered_map<int, int> map;
for(int i = start; i < nums.size(); ++i){
int val = target - nums[i];
if(map.find(val) != map.end()){
if(map[val] != 0)
res.push_back({nums[start-1], val, nums[i]});
map[val] = 0; //打标记,说明这个数已经被添加过了
}
else map[nums[i]] = 1;
}
}
vector<vector<int>> threeSum(vector<int>& nums) {
if(nums.size() < 3) return {};
vector<vector<int>> res;
sort(nums.begin(), nums.end());
for(int i = 0; i < nums.size()-2; ++i){
if(i != 0 && nums[i] == nums[i-1]) continue;//和上一个元素相同直接跳过,避免重复查找
twosum(nums, res, i+1, -1*nums[i]);
}
return res;
}
};
上面的思路确实暴力了,在求两数之和的时候每次都构建map耗时又耗空间,看看能怎么优化吧,首先三数之和转化为两数之和这一步应该是没问题的,第一次遍历不可避免,而且目前我觉的排序也是必要的,因为不排序很难去重。
那关键还是在于两数之和的处理,如果我们不使用哈希表处理,那么很直接的处理思路就是两次遍历了,就和leetcode第一题很像,两次遍历的复杂度必然是无法接受的。
双指针法
有一次遍历的办法吗?可以看到我们使用哈希表处理的时候没有用到数组有序这个重要特点,我们不是已经排过序了吗,那就把有序这个特点利用起来,两个指针分别指向区间首尾,从两边向中间查找就可以了:
class Solution {
public:
vector<vector<int>> res;
vector<vector<int>> threeSum(vector<int>& nums) {
int n = nums.size();
if(nums.size() < 3) return res;
sort(nums.begin(), nums.end());
for(int i = 0; i < n-2; ++i){
if(i != 0 && nums[i] == nums[i-1]) continue;//去重
int l = i+1, r = n-1;//双指针左右边界
while(l < r){
if(nums[i] + nums[l] + nums[r] > 0) --r;
else if(nums[i] + nums[l] + nums[r] < 0) ++l;
else{
res.push_back({nums[i], nums[l++], nums[r--]});
while(l < r && nums[l] == nums[l-1]) ++l;//去重
while(l < r && nums[r] == nums[r+1]) --r;
}
}
}
return res;
}
};
ok,现在的效率好了很多,代码也更简洁了,那还可以继续优化吗?重点还是排序后的数组,因为数组有序,如果nums[i]>0,那么nums[i]就没有必要向后检索了,因为不可能存在j和k使得三者和为0,因为后面的数都是大于等于nums[i]的。
因为三个数相加为0,除了000的情况,那就至少有一个负数,至少有一个正数
class Solution {
public:
vector<vector<int>> res;
vector<vector<int>> threeSum(vector<int>& nums) {
int n = nums.size();
if(nums.size() < 3) return res;
sort(nums.begin(), nums.end());
for(int i = 0; i < n-2 && nums[i] <= 0; ++i){
if(i != 0 && nums[i] == nums[i-1]) continue;//去重
int l = i+1, r = n-1;//双指针左右边界
while(l < r){
if(nums[i] + nums[l] + nums[r] > 0) --r;
else if(nums[i] + nums[l] + nums[r] < 0) ++l;
else{
res.push_back({nums[i], nums[l++], nums[r--]});
while(l < r && nums[l] == nums[l-1]) ++l;//去重
while(l < r && nums[r] == nums[r+1]) --r;
}
}
}
return res;
}
};