今天晚上没啥事,就挑战了一题中等题,结果果然没写出来,思路应该是没有问题,因为是卡在了一个大输入,超时了,查阅了官方的答案,感觉是还有些重复的地方没有考虑周全。
【题目描述】
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
【示例】
示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
示例 2:
输入:nums = []
输出:[]
示例 3:
输入:nums = [0]
输出:[]
【解题过程】
首先,我认为应该是要枚举,但是对于三个数的和,用三层循环的话,显然会超时(虽然自己写的两层都超时了),因此,使用两层循环。
首先,解决特殊情况:
当数组中的数据不足三个,答案肯定是空。
而后,进行两层循环的遍历。在遍历时,考虑到会有重复,因此,添加了一些判定条件,如:三个数的和为0,必然是两种情况:
- 0+0+0
- 三个数中至少有一个负数
因此,不妨将第一层循环中的数字设定为非正数,则可以减少近一半的重复情况。为了方便遍历,先对数组进行了排序。
此外,第二层应该在第一层的右边,若在左边,也会造成重复。
基于上述思路,我写了如下代码:
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> ans;
if(nums.size()==0 || nums.size()==1 || nums.size()==2)
{
return ans;
}
sort(nums.begin(),nums.end());//排序
// int a=0,b=0,c=0;
for(vector<int>::iterator i=nums.begin();(i<nums.end()-1) &&(*i<=0);i++){
for(vector<int>::iterator j=nums.end()-1;(j>i)&&(*j>=0);j--){
//两个数字不应该是同一个,且只需要比较i<j时的情况,当i>j,重复
vector<int> temp;
int f=0-(*i+*j);
vector<int>::iterator ind=find(nums.begin(),nums.end(),f);
if((ind==nums.end())||(ind==i)||(ind==j)){//未找到或与i/j重复
continue;
}
else{
temp.push_back(*i);
temp.push_back(*j);
temp.push_back(f);
sort(temp.begin(),temp.end());//排序
vector<vector<int>>::iterator t=find(ans.begin(),ans.end(),temp);
if(t==ans.end()){
ans.push_back(temp);//不会重复,添加
}
}
}
}
sort(ans.begin(),ans.end());
return ans;
}
};
然而结果是超时:
【参考代码】
想了很久都没有想出来要怎么优化,因此,看了官方的参考代码:
官方解决方案
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;
}
};
经过比对,发现主要是忽略了这种重复的情况:
因此,我决定再对我的代码进行修改。
【艰难修改】
然后,我将代码改成了如下形式:
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> ans;
if(nums.size()==0 || nums.size()==1 || nums.size()==2)
{
return ans;
}
if(nums.size()==3){
if(nums[0]+nums[1]+nums[2]==0){
ans.push_back(nums);
}
return ans;
}
sort(nums.begin(),nums.end());//排序
for(int i=0;i<nums.size();++i){
if((i>0 && nums[i]==nums[i-1])||(nums[i]>0))
{
continue;
}
for(int j=i+1;j<nums.size();++j){
//两个数字不应该是同一个,且只需要比较i<j时的情况,当i>j,重复
vector<int> temp;
int f=0-(nums[i]+nums[j]);
vector<int>::iterator it=find(nums.begin(),nums.end(),f);
int ind=&*it - &nums[0];//将迭代器转换为下标
if((it==nums.end())||(ind==i)||(ind==j)){//未找到或与i/j重复
continue;
}
else{
temp.push_back(nums[i]);
temp.push_back(nums[j]);
temp.push_back(*it);
sort(temp.begin(),temp.end());//排序
vector<vector<int>>::iterator t=find(ans.begin(),ans.end(),temp);
if(t==ans.end()){
ans.push_back(temp);//不会重复,添加
}
}
}
}
sort(ans.begin(),ans.end());
return ans;
}
};
结果还是错误,显示为:
想了很久很久,终于,我意识到,应该是用的find函数有问题,因为输入四个都是0,而我要使用find找的也是0,很有可能,它返回的位置,是第一个0,这样转换为下标后,我得到的ind==i,因此,它执行了continue语句,我无法得到正确答案。
为了验证这个想法,我在控制台输出,果然。。
因此,只能改变思路。。。抛弃find方案。
将代码改成了三层for循环,
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> ans;
if(nums.size()==0 || nums.size()==1 || nums.size()==2)
{
return ans;
}
if(nums.size()==3){
if(nums[0]+nums[1]+nums[2]==0){
ans.push_back(nums);
}
return ans;
}
sort(nums.begin(),nums.end());//排序
for(int i=0;i<nums.size();++i){
if((i>0 && nums[i]==nums[i-1])||(nums[i]>0))
{
continue;
}
for(int j=i+1;j<nums.size();++j){
//两个数字不应该是同一个,且只需要比较i<j时的情况,当i>j,重复
if(j>i+1 && nums[j]==nums[j-1])
{
continue;
}
for(int k=nums.size()-1;k>j;k--)
{
vector<int> temp;
if(nums[i]+nums[j]+nums[k]<0){
break;
}
if(nums[i]+nums[j]+nums[k]==0){
temp.push_back(nums[i]);
temp.push_back(nums[j]);
temp.push_back(nums[k]);
sort(temp.begin(),temp.end());//排序
vector<vector<int>>::iterator t=find(ans.begin(),ans.end(),temp);
if(t==ans.end()){
ans.push_back(temp);//不会重复,添加
}
}
}
}
}
sort(ans.begin(),ans.end());
return ans;
}
};
结果,果然在第315个测试点超时了。。。。。。。。。感觉今天改不出来了,图书馆也要闭馆了,明天继续肝吧。【果然还是太菜,一个中等题做了三个小时,看了答案还改不出来】
------------------3.3更新------------------------------------------------------------
改了半天,还是过不去,最后改成了参考答案的同款,结果依然超时,细看了很久,发现我用的是k–,而答案用的是–k,抱着试一试的心态,结果就过了?????????????
对应的代码:
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> ans;
if(nums.size()==0 || nums.size()==1 || nums.size()==2)
{
return ans;
}
if(nums.size()==3){
if(nums[0]+nums[1]+nums[2]==0){
ans.push_back(nums);
}
return ans;
}
sort(nums.begin(),nums.end());//排序
for(int i=0;i<nums.size();++i){
if((i>0 && nums[i]==nums[i-1])||(nums[i]>0))
{
continue;
}
int k=nums.size()-1;
for(int j=i+1;j<nums.size();++j){
//两个数字不应该是同一个,且只需要比较i<j时的情况,当i>j,重复
if(j>i+1 && nums[j]==nums[j-1])
{
continue;
}
while ((k>j) && (nums[i]+nums[j]+nums[k]>0))
{
--k; //这里变成k--就会超时。。。。。。。。。。。
}
if(j==k){
break;
}
if(nums[i]+nums[j]+nums[k]==0)
ans.push_back({nums[i],nums[j],nums[k]});
}
}
sort(ans.begin(),ans.end());
return ans;
}
};
【总结】
这题写下来,明显感觉到自己代码能力有多差劲,做为大三的计院学子,属实不应该,这样下去面试要咋整诶!!!!!!!总觉得自己的思路和参考答案是一样的,但写出来和跑起来的结果相差就是很大。还是要多练习,找找解题规律吧,希望能坚持下去。
此外,第一次意识到了–k和k–的不同之处,以前老师讲过,也知道一个是先减后用,另一个是先用后减,但一直觉得没啥区别,这次是真实感受到了这种区别了。。。。看来只是还是要用才能理解,之前都只是停留在课本上了。可惜啊可惜。。