和之前的两数之和问题一样,这个问题也可以直接三重循环直接莽穿,但是这算法时间复杂度太大了,O(n^3)的时间复杂度一旦遇到1000位的数组就要循环10亿次,几万位的话时间够泡一杯java了。
这个问题有两种解法,时间复杂度都是O(n^2),包括散列表查找法和双指针法。
散列表法当然就是之前搞两数之和算法的最优解法,然而幸亏我提前看了讨论区说明这种方法会超时,就没有试验,不然就会写老半天程序最后答案还超时心态爆炸。其实,散列表的查询虽然是线性时间,但是散列表对空间占用非常大,而且碰撞处理也会拖慢它的查找时间使得查找的效率变慢。
然后我就使用了双指针法,先对整个数组进行排序,然后具体原理看两数之和就可以了。之前还有一篇专门写双指针的文章。就是一个指针移动使结果变大,一个使得结果变小。
但是!再次读题,会发现题目要求答案不能包含重复的组。就算结果在程序中算出了两次,最后返回的数组中也只能有一个。刚刚发现这个问题的时候,我认为这个问题很好解决,只要在最后再添加一个小程序来判断解是否在数组中就可以了。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
if(nums.size()<3) return {};
sort(nums.begin(), nums.end());
int lptr=0;
int rptr=0;
int target=0;
vector<vector<int>> res;
for(int i=0;i<nums.size()-2;i++){
target=-nums[i];
lptr=i+1;
rptr=nums.size()-1;
while(rptr>lptr){
if(nums[lptr]+nums[rptr]>target){
rptr--;
}else if(nums[lptr]+nums[rptr]<target){
lptr++;
}else{
bool isbreak=false;
for(int a=0;a<res.size();a++){
if(res[a][0]==nums[i]&&res[a][1]==nums[lptr]&&res[a][2]==nums[rptr]) isbreak=true;
}
if(!isbreak)
res.push_back({nums[i],nums[lptr],nums[rptr]});
lptr++;
rptr--;
}
}
}
return res;
}
};
题目确实可以这样解出来,但是这样做,最终的时间评分只有5%具体原因,我估计就是最后一小段程序拖慢了时间。
假设有一个输入是[0,0,0,0,0,0,0,0,0.....,0](上千个0),这时判断是否有重复元素的程序循环的次数可想而知非常多,可以把时间复杂度拖到O(n^3)。
所以就要对程序进行再优化。我们想可不可以在循环的时候就判断出结果是否重复,去掉最后判断结果是否重复的程序?考虑一个现象,数组既然已经排序好了,那么同样的元素必定相邻。那么如果这次循环的元素和上次一样,这次循环就包括了上次循环的所有可能情况,这次循环就不能执行,必须马上跳出以保证结果中同样的答案只出现一次。改进代码如下:
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
if(nums.size()<3) return {};
sort(nums.begin(), nums.end());
int lptr=0;
int rptr=0;
int target=0;
vector<vector<int>> res;
for(int i=0;i<nums.size()-2;i++){
target=-nums[i];
if(nums[i]>0) break;
if(i > 0 && nums[i] == nums[i - 1]) continue;
lptr=i+1;
rptr=nums.size()-1;
while(rptr>lptr){
if(nums[lptr]+nums[rptr]>target){
rptr--;
while(lptr<rptr&&nums[rptr]==nums[rptr+1]) rptr--;
}else if(nums[lptr]+nums[rptr]<target){
lptr++;
while(lptr<rptr&&nums[lptr]==nums[lptr-1]) lptr++;
}else{/*
bool isbreak=false;
for(int a=0;a<res.size();a++){
if(res[a][0]==nums[i]&&res[a][1]==nums[lptr]&&res[a][2]==nums[rptr]) isbreak=true;
}
if(!isbreak)
*/
res.push_back({nums[i],nums[lptr],nums[rptr]});
lptr++;
while(lptr<rptr&&nums[lptr]==nums[lptr-1]) lptr++;
rptr--;
while(lptr<rptr&&nums[rptr]==nums[rptr+1]) rptr--;
}
}
}
return res;
}
};
结果达到了81%,说明算法还有优化空间,但是做题的时候心态爆炸,没有想出来,试一下解答区大佬的优化技巧(++i的使用一看就知道是C++编程老手)
貌似编译器会对这样的程序进行优化以达到更加的效率,这点我就不懂了。。。不过既然时间复杂度是一样的,差距也没多少,这些内容的研究还是次要的。掌握主要的算法知识才是重点。