Medium!
题目描述:
给定一个包含 n 个整数的数组 nums
,判断 nums
中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4], 满足要求的三元组集合为: [ [-1, 0, 1], [-1, -1, 2] ]
解题思路:
这道题让我们求三数之和,比之前那道Two Sum要复杂一些,考虑过先fix一个数,然后另外两个数使用Two Sum那种HashMap的解法,但是会有重复结果出现,就算使用set来去除重复也不行,会TLE,看来此题并不是考我们Two Sum的解法。那么我们来分析一下这道题的特点,要我们找出三个数且和为0,那么除了三个数全是0的情况之外,肯定会有负数和正数,我们还是要先fix一个数,然后去找另外两个数,我们只要找到两个数且和为第一个fix数的相反数就行了,既然另外两个数不能使用Two Sum的那种解法来找,如果能更有效的定位呢?我们肯定不希望遍历所有两个数的组合吧,所以如果数组是有序的,那么我们就可以用双指针以线性时间复杂度来遍历所有满足题意的两个数的组合。
我们对原数组进行排序,然后开始遍历排序后的数组,这里注意不是遍历到最后一个停止,而是遍历到倒数第三个就可以停止了。这里我们可以先做个剪枝优化,就是当遍历到正数的时候就break,为啥呢,因为我们的数组现在是有序的了,如果第一个要fix的数就是正数了,那么后面的数字就都是正数,就永远不会出现和为0的情况了。然后我们还要加上重复就跳过的处理,处理方法是从第二个数开始,如果和前面的数字相等,就跳过,因为我们不想把相同的数字fix两次。对于遍历到的数,用0减去这个fix的数得到一个target,然后只需要再找到两个数之和等于target即可。我们用两个指针分别指向fix数字之后开始的数组首尾两个数,如果两个数和正好为target,则将这两个数和fix的数一起存入结果中。然后就是跳过重复数字的步骤了,两个指针都需要检测重复数字。如果两数之和小于target,则我们将左边那个指针i右移一位,使得指向的数字增大一些。同理,如果两数之和大于target,则我们将右边那个指针j左移一位,使得指向的数字减小一些,代码如下:
C++参考答案一:
1 class Solution { 2 public: 3 vector<vector<int>> threeSum(vector<int>& nums) { 4 vector<vector<int>> res; 5 sort(nums.begin(), nums.end()); 6 for (int k = 0; k < nums.size(); ++k) { 7 if (nums[k] > 0) break; 8 if (k > 0 && nums[k] == nums[k - 1]) continue; 9 int target = 0 - nums[k]; 10 int i = k + 1, j = nums.size() - 1; 11 while (i < j) { 12 if (nums[i] + nums[j] == target) { 13 res.push_back({nums[k], nums[i], nums[j]}); 14 while (i < j && nums[i] == nums[i + 1]) ++i; 15 while (i < j && nums[j] == nums[j - 1]) --j; 16 ++i; --j; 17 } else if (nums[i] + nums[j] < target) ++i; 18 else --j; 19 } 20 } 21 return res; 22 } 23 };
或者我们也可以利用set的不能包含重复项的特点来防止重复项的产生,那么我们就不需要检测数字是否被fix过两次,不过个人觉得还是前面那种解法更好一些,参见代码如下:
C++参考答案二:
1 class Solution { 2 public: 3 vector<vector<int>> threeSum(vector<int>& nums) { 4 set<vector<int>> res; 5 sort(nums.begin(), nums.end()); 6 for (int k = 0; k < nums.size(); ++k) { 7 if (nums[k] > 0) break; 8 int target = 0 - nums[k]; 9 int i = k + 1, j = nums.size() - 1; 10 while (i < j) { 11 if (nums[i] + nums[j] == target) { 12 res.insert({nums[k], nums[i], nums[j]}); 13 while (i < j && nums[i] == nums[i + 1]) ++i; 14 while (i < j && nums[j] == nums[j - 1]) --j; 15 ++i; --j; 16 } else if (nums[i] + nums[j] < target) ++i; 17 else --j; 18 } 19 } 20 return vector<vector<int>>(res.begin(), res.end()); 21 } 22 };
知识点补充:
set的特性是,所有元素都会根据元素的键值自动排序,set的元素不像map那样可以同时拥有实值(value)和键值(key),set元素的键值就是实值,实值就是键值。set不允许两个元素有相同的键值。使用的时候要包含头文件“#include<set>”。
set的各成员函数列表如下:
1. begin()--返回指向第一个元素的迭代器
2. clear()--清除所有元素
3. count()--返回某个值元素的个数
4. empty()--如果集合为空,返回true
5. end()--返回指向最后一个元素的迭代器
6. equal_range()--返回集合中与给定值相等的上下限的两个迭代器
7. erase()--删除集合中的元素
8. find()--返回一个指向被查找到元素的迭代器
9. get_allocator()--返回集合的分配器
10. insert()--在集合中插入元素
11. lower_bound()--返回指向大于(或等于)某值的第一个元素的迭代器
12. key_comp()--返回一个用于元素间值比较的函数
13. max_size()--返回集合能容纳的元素的最大限值
14. rbegin()--返回指向集合中最后一个元素的反向迭代器
15. rend()--返回指向集合中第一个元素的反向迭代器
16. size()--集合中元素的数目
17. swap()--交换两个集合变量
18. upper_bound()--返回大于某个值元素的迭代器
19. value_comp()--返回一个用于比较元素间的值的函数
1 #include<set> 2 #include<iostream> 3 using namespace std; 4 int main() 5 { 6 int i; 7 int arr[5] = {0,1,2,3,4}; 8 set<int> iset(arr,arr+5); 9 10 iset.insert(5); 11 cout<<"size:"<<iset.size()<<endl; 12 cout<<"3 count = "<<iset.count(3)<<endl; 13 iset.erase(1); 14 15 set<int>::iterator ite1 = iset.begin(); 16 set<int>::iterator ite2 = iset.end(); 17 for(;ite1!=ite2;ite1++) 18 { 19 cout<<*ite1; 20 } 21 cout<<endl; 22 23 ite1 = iset.find(3); 24 if(ite1!=iset.end()) 25 cout<<"3 found"<<endl; 26 27 ite1 = iset.find(1); 28 if(ite1!=iset.end()) 29 cout<<"1 not found"<<endl; 30 }