题目大意:
在一个数组中求出所有a,b,c,使得a+b+c=0,注意元素不能重复,且(a,b,c)不能重复。
分析:
(一)最直接的想法是直接枚举a,b,c,这样的做法是O(n^3),很明显会超时。
(二)接下来有两个想法,我最初想到的是首先排序,然后枚举a和b,然后在剩余元素中二分查找判断是否存在-(a+b),这样的时间复杂度是O(n^2logn),另一个想法是利用哈希表来判断是否存在-(a+b),前者的时间复杂度是O(n^2logn),后者则是O(n^2),然而神奇的是我用二分查找的算法居然过了(虽然排在很靠后的位置),而哈希表的居然超时。
(三)最后的一个想法,首先排序,接着顺序枚举a,然后在a后面的元素中使用双指针:
假设a=nums[i](i可以从0取到最后),后面的区间为[i+1, nums.size() - 1],然后令left=i+1,right=nums.size()-1,即b=nums[left],c=nums[right],之后我们计算nums[left] + nums[right] + nums[i]是否为0,如果等于0,那么记录该组解,如果大于0,那么说明c太大,需要将right向左移,小于0则说明b太小,需要向右移动,直至两个指针相遇。注意:在移动的过程中如果遇到重复的元素要及时跳过。
这种思想类似于首先固定一个元素,然后再在线性时间复杂度内解决一个问题,比哈希表的算法快的原因我认为主要是能够及时地处理重复元素带来的多余计算,同时空间复杂度更小,仅为O(1)。
代码:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> rs;
int len = nums.size();
if(len == 0) {
return rs; //极端情况,给了一个空数组
}
sort(nums.begin(), nums.end());
for(int i = 0; i < nums.size(); i++) {
if(i > 0 && nums[i] == nums[i - 1]) {
continue; //如果a存在重复元素则不断向右寻找新的元素
}
int left = i + 1, right = nums.size() - 1;
while(left < right) {
int tag = nums[i] + nums[left] + nums[right];
if(tag == 0) {
rs.push_back({nums[i], nums[left], nums[right]});
left++;
right--;
while(left < right && nums[left] == nums[left - 1]) {
left++; //判断b是否存在重复的元素
}
while(left < right && nums[right] == nums[right + 1]) {
right--; //判断c是否存在重复的元素
}
} else if(tag > 0) {
right--;
} else {
left++;
}
}
}
return rs;
}