3Sum
直接三层循环会超时
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<int> temp;
vector<vector<int>> res;
int n=nums.size();
for(int i=0;i<n-2;i++){
for(int j=i+1;j<n-1;j++){
for(int t=j+1;t<n;t++){
if(nums[i]+nums[j]+nums[t]==0){
temp.push_back(nums[i]);
temp.push_back(nums[j]);
temp.push_back(nums[t]);
sort(temp.begin(),temp.end());//对vector内元素进行排序,便于后面去重
res.push_back(temp);
temp.clear();
}
}
}
}
set<vector<int>> st(res.begin(), res.end());//转换为集合set去重
res.assign(st.begin(), st.end());
//res.erase(unique(res.begin(), res.end()), res.end());
return res;
}
};
分析一下这道题的特点,要找出三个数且和为0,那么除了三个数全是0的情况之外,肯定会有负数和正数。可以先fix一个数,然后去找另外两个数,只要找到两个数和为第一个fix数的相反数就行。既然另外两个数不能使用Two Sum的那种解法来找,如何能更有效的定位呢?遍历所有两个数的组合不是最有效的,但如果数组是有序的,那么就可以用双指针以线性时间复杂度来遍历所有满足题意的两个数组合。
对原数组进行排序,然后开始遍历排序后的数组,注意不是遍历到最后一个停止,而是到倒数第三个。这里可以先做个剪枝优化,就是当遍历到正数的时候就break,因为数组现在是有序的,如果第一个要fix的数就是正数那么后面的数字就都是正数,就永远不会出现和为0的情况。然后还要加上重复就跳过的处理,处理方法是从第二个数开始,如果和前面的数字相等,就跳过,因为不用把相同的数字fix两次。
对于遍历到的数,用0减去这个fix的数得到一个target,然后只需其后找到两个数之和等于target即可。用两个指针分别指向fix数字之后开始的数组首尾两个数,如果两个数和为target,则将这两个数和fix的数一起存入结果中。然后跳过重复数字,两个指针都需要检测重复数字。如果两数之和小于target,则将左边那个指针i右移一位,使得指向的数字增大一些。同理,如果两数之和大于target,则将右边那个指针j左移一位,使得指向的数字减小一些。代码如下:
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> res;
sort(nums.begin(), nums.end());
if (nums.empty() || nums.back() < 0 || nums.front() > 0) return {};
for (int k = 0; k < nums.size(); k++) {
if (nums[k] > 0) break;
if (k > 0 && nums[k] == nums[k - 1]) continue;
int target = 0 - nums[k];
int i = k + 1, j = nums.size() - 1;
while (i < j) {
if (nums[i] + nums[j] == target) {
res.push_back({nums[k], nums[i], nums[j]});
while (i < j && nums[i] == nums[i + 1]) i++;
while (i < j && nums[j] == nums[j - 1]) j--;
i++;
j--;
} else if (nums[i] + nums[j] < target) i++;
else j--;
}
}
return res;
}
};
或者也可以利用集合set的不能包含重复项的特点来防止重复项的产生,就不需要检测数字是否被fix过两次,不过还是前面那种解法更好一些。代码如下:
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
set<vector<int>> res;
sort(nums.begin(), nums.end());
if (nums.empty() || nums.back() < 0 || nums.front() > 0) return {};
for (int k = 0; k < nums.size(); ++k) {
if (nums[k] > 0) break;
int target = 0 - nums[k];
int i = k + 1, j = nums.size() - 1;
while (i < j) {
if (nums[i] + nums[j] == target) {
res.insert({nums[k], nums[i], nums[j]});
i++;
j--;
} else if (nums[i] + nums[j] < target) i++;
else j--;
}
}
return vector<vector<int>>(res.begin(), res.end());
}
};
补充
vector是一个数组但是它的占用的内存大小是动态变化的。当vector占用的内存满了之后就要重新分配内存,并且赋值原来的所有元素。为了避免频繁的重新分配内存迁移数据,vector实际分配的内存比需要的内存多。比如有10个int的数据在vector中,vector实际占用的内存是20个int的内存, 当数据占用超过实际占用内存的比例时,vector就会自动重新分配内存,迁移数据. vector实际占用的内存可以用capacity()来查看。
#include<iostream>
#include<vector>
using namespace std;
int main()
{
vector<int> ans;
for(int i=0; i<10; i++) ans.push_back(i);
ans.erase(ans.begin()+2);
cout<<"擦除第三个数字:";
for(int j=0; j<ans.size(); j++) cout<<ans[j]<<" ";
ans.erase(ans.begin(), ans.begin()+2);
cout<<endl<<"擦除前2个数字:";
for(int k=0; k<ans.size(); k++) cout<<ans[k]<<" ";
ans.insert(ans.begin()+1, 100);
cout<<endl<<"在第一位后面插入100:";
for(int m=0; m<ans.size(); m++) cout<<ans[m]<<" ";
//vector在声明的时候,可以申明大小和默认值
vector<int> temp(5, -1);
cout<<endl<<"temp的大小为5,默认值是-1:";
for(int l=0; l<temp.size(); l++) cout<<temp[l]<<" ";
//resize(int n)改变vector实际储存的数据个数, 如果n比实际个数多,则多出的位添加0,否则截取掉多余数据
temp.resize(8);
cout<<endl<<"把temp的大小改变位8:";
for(int h=0; h<temp.size(); h++) cout<<temp[h]<<" ";
//在改变vector大小的同时还能指定多余内存的值;这种方式只适用于分配的空间比原来的多的情况
temp.resize(10, 1111);
cout<<endl<<"temp的大小改为10,并且指定多出来空间的值位11111:";
for(int g=0; g<temp.size(); g++)cout<<temp[g]<<" ";
cout<<endl<<"获取temp的第一个元素:"<<temp.front()<<endl<<"获取temp的最后一个元素:"<<temp.back();
//常用empty()和size函数来判断vector是否为空,当vector为空的时候, empty()返回true, size()的值为0
return 0;}
//可以配合#include<algorithm>库中的unique函数来删除vector中的重复元素
vector<int> ans;
ans.erase(unique(ans.begin(), ans.end()), ans.end());
return {};
indicates "return an object of the function's return type initialized with an empty list-initializer".
The exact behaviour depends on the returned object's type.
return {};指示“返回用空列表初始化器初始化的函数返回类型的对象”。
确切的行为取决于返回对象的类型。