15:三数之和
题目:
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
思路1:
三重循环暴力,注意到每重循环中增加一个判断机制进行去重,这个之前做到过,可以先对数组sort,相同的数字肯定是邻接着的。我们让每次循环时候因为一共是i j k 三个数字,我们让i j k这三位数字中对应的各种种类数字最多出现一次! 比如0 12 ,我们下次i遇到0就直接break了,因为已经有0给他探路了。
但是比如012 或者210不还是重复了么,所以排序的还有一个好处是从左到右是递增的或者不变的,不可能在012后出现210的组合,所以排序后我们只需要考虑对应位或者在搜索树中对应深度位某个数字只出现1次即可。排除如 012 和 012相同的组合。
但是暴力法还是超时了 315/318
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
int n=nums.size();
if(n<3)
return {};
//先试试看暴力法
vector<vector<int>> res;
sort(nums.begin(),nums.end());
for(int i=0;i<n-2;i++){
if(i>0 && nums[i]==nums[i-1]) continue;
for(int j=i+1;j<n-1;j++){
if(j>i+1 && nums[j]==nums[j-1]) continue;
for(int k=j+1;k<n;k++){
if(k>j+1 && nums[k-1]==nums[k]) continue;
vector<int> temp;
if((nums[i]+nums[j]+nums[k])==0){
temp.push_back(nums[i]);
temp.push_back(nums[j]);
temp.push_back(nums[k]);
res.push_back(temp);
}
}
}
}
return res;
}
};
思路2:
也是先sort,第一位就直接遍历,然后找后面两位,调用twosum,然后面两位的和等于我们目前nums[i]的相反数。这个twosum函数使用双指针来查找。指针的头是i+1,尾是end每次都是这样,因为我们是排序的,就是从头和尾来查找之和是否等于目标值,如果小了那要增加,就是begin++,如果大了,减小,最有效的减小方式就是通过end–,反正要是存在肯定在两端会出现。然后至少2个所以start<end,查找的时间复杂度是n,总的时间复杂度就是n的平方!
这里有个骚操作: 二维数组vector可以接收一个不定尺寸的数组相等。然后我们平时都是一维插入二维数组中,二维度数组插入二维度数组可以用insert指令
//vector<vector<int>>temp;
auto temp=twosum(i+1,n-1,nums,nums[i],-nums[i]);
res.insert(res.end(),temp.begin(),temp.end()); //在res的末尾插入一行行
class Solution {
public:
vector<vector<int>> twosum(int begin,int end,vector<int>& nums,int value,int target){
vector<vector<int>> temp;
while(begin<end){ //因为只要要两个
int sum=nums[begin]+nums[end];
if(sum==target){
vector<int>result;
result.push_back(value);
result.push_back(nums[begin]);
result.push_back(nums[end]);
temp.push_back(result);
//去重,在操作完一个数据后要避免重复解
while(begin<end && nums[begin]==nums[begin+1])
begin++;
begin++;
while(begin<end && nums[end]==nums[end-1])
end--;
end--;
}
//判断其他情况,不行就缩进 没录取所用不用大费周章。
else if(sum<target)
begin++;
else if(sum>target)
end--;
}
return temp;
}
vector<vector<int>> threeSum(vector<int>& nums) {
sort(nums.begin(),nums.end());
vector<vector<int>>res;
int n=nums.size();
for(int i=0; i<n; i++){
if(i>0 && nums[i]==nums[i-1])
continue;
vector<vector<int>>temp;
temp=twosum(i+1,n-1,nums,nums[i],-nums[i]);
res.insert(res.end(),temp.begin(),temp.end());
}
return res;
}
};
思路3:
用哈希表来减少时间复杂度,这个C++的哈希表不是很好,哎不能通过value查找key,所以有点晦涩难懂,这里的j就是2 3个元素一起查找了。然后j其实是更大的元素,c反而是中间的元素。
为什么要找3个相同的情况才break,因为我们说了j是同时找2 3个元素,我们允许-4 22的存在,但是不允许-4 222。因为两个相同的,就会抵消,然后-4 2如果后面还遇到2的话怎么办相当于你把这个第三个2给用在了第二个位置,所以要去重。
然后哈希表里面的存的是第三个位置的,但是注意到我们这里第三个位置也是小于小于第二个位置的值的。用过了就要删除,不然下次再次遇到和第二个值一样的就重复了。
举例就是;
-2 0 0 2 2 如果哈希表不抹除会重复。
-4 2 2 2 如果3个相邻的不判断的话会重复j的取值。。。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(), nums.end());
// 找出a + b + c = 0
// a = nums[i], b = nums[j], c = -(a + b)
for (int i = 0; i < nums.size(); i++) {
// 排序之后如果第一个元素已经大于零,那么不可能凑成三元组
if (nums[i] > 0) {
break;
}
if (i > 0 && nums[i] == nums[i - 1]) { //三元组元素a去重
continue;
}
unordered_set<int> set; //注意哈希表的位置,在j之前 每次i之后开始建立
for (int j = i + 1; j < nums.size(); j++) { //j<nums.size()因为同时在查找
//因为这个j不是单纯的第二个元素,不能直接进行判断x+y是否大于0.这里j是同时在查找2 3个数。
if (j > i + 2 && nums[j] == nums[j-1] && nums[j-1] == nums[j-2]) { // 三元组元素b去重
continue;
}
int c = 0 - (nums[i] + nums[j]);
if (set.count(c)) { //如果哈希表value出现了值c就为1
result.push_back({nums[i], nums[j], c});
set.erase(c);// 三元组元素c去重
}
else {
set.insert(nums[j]);
}
}
}
return result;
}
};
进一步地,看下这道两数之和的题目:
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int,int> map;
for(int i=0; i<nums.size(); i++){
//从地址方面来索引啊 可以用find,这个类似从哈希表中抽出地址
auto it=map.find(target-nums[i]);
if(it!=map.end())
return {it->second,i};
else{
map[nums[i]]=i;
}
}
return {};
}
};
如果把两数之和改下,改成找出其中所有等于target的组合,那么我就可以对数组进行排序,然后用双指针来缩小区间查找解了!