3sum
题目
给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/3sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
第一时间想到暴力算法采用3重for循环,遍历所有3个数的可能组合,找到和为0的,并且丢掉重复的组合即可。但是这样复杂度过高 O ( n 3 ) O(n^3) O(n3),没有尝试,应该会超时。
观看了Krahets的题解,加上自己的理解写下这篇文章:先假定数组第一个数为3数和为0的其中一个,然后在剩下的数中枚举所有2数组合,找其他两个数,使得这3个数之和为0,如果找到记录下结果,枚举完后假定数组中下一个数为3数和中的一个,伪代码如下:
idx1 = 0//假定nums[idx1]能与nums[idx2],nums[idx3]相加和为0
whie (idx1< nums.size()-2) {
for (idx2 = idx1+1; idx2 < nums.size(); idx2++)
for (idx3 = idx2+1; idx3 < nums.size(); idx3++)
if (nums[idx1] + nums[idx2] + nums[idx3] == 0) 记录到结果中
idx1++;
}//这里没有考虑怎么去重
如果这里枚举所有2数组合采用两重for循环,那么上面的算法仍然是 O ( n 3 ) O(n^3) O(n3),但是可以利用双指针代替两重for循环,用双指针枚举时间复杂度为O(n)
具体的思路如下,要想使用双指针,先对nums数组进行排序(从小到大),时间复杂度为O(nlogn),设置3个指针,idx1,idx2,idx3,idx1初始指向数组的起始位置idx1=0,idx2=idx+1,idx3=nums.size()-1,现在目标变为在数组nums[idx2-idx3]中到两个数,使得nums[idx1]+nums[idx2]+nums[idx3] = 0。
设sum = nums[idx1] + nums[idx2] + nums[idx3],如果sum<0,那么只有将其中一个数增大才有可能使得sum=0,因为nums数组是有序的,只需要将idx2++即可,因为nums[idx2] <= nums[idx2+1],增加nums[idx2]的值才有可能使得sum从小于0转变为等于0。另一种情况,如果sum>0,这时只有让idx3–才有可能使sum从大于0转变为等于0。最后如果sum=0,则记录nums[idx1],nums[idx2],nums[idx3]到结果中,同时,idx2++,idx3–,此时必须让idx2和idx3跳过相同的元素直至一个新的元素,不然找到的结果会有重复的。当idx2==idx3时上述枚举过程结束,idx1++(此时同样需要跳过与nums[idx1]相同的元素,不然会有重复结果),idx2=idx1+1,idx3=nums.size()-1,开启下一轮枚举,上述算法时间复杂度为 O ( n 2 ) O(n^2) O(n2)
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> rst;
if (nums.size() < 3) return rst;
sort(nums.begin(), nums.end());
int idx1 = 0, idx2, idx3;
while (idx1 < nums.size()-2) {//固定nums[idx1],找另外两个数与之相加结果为0
if (nums[idx1] > 0) break;//因为数组有序,nums[idx2],nums[idx3]>nums[idx1]此时不可能有3个数和为0
idx2 = idx1+1;
idx3 = nums.size()-1;
while (idx2 < idx3) {//使用双指针查找nums[idx1] + nums[idx2] + nums[idx3] == 0
int sum = nums[idx1] + nums[idx2] + nums[idx3];
if (sum > 0) {
idx3--;//要想和为0,只能移动idx3才能使3个数的和朝着和为0的目标靠近
//while (idx2 < idx3 && nums[idx3] == nums[idx3+1]) idx3--;//跳过所有相同的元素,这行非必须
}
if (sum < 0) {
idx2++;//要想和为0,只能移动idx2才能使3个数的和朝着和为0的目标靠近
//while (idx2 < idx3 && nums[idx2] == nums[idx2-1]) idx2++;//跳过所有相同的元素,这行非必须
}
if (sum == 0) {
rst.push_back({nums[idx1], nums[idx2], nums[idx3]});
idx2++;
while (idx2 < idx3 && nums[idx2] == nums[idx2-1]) idx2++;//跳过所有相同的元素,如果不跳过得到的结果中有相同的
idx3--;
while (idx2 < idx3 && nums[idx3] == nums[idx3+1]) idx3--;//跳过所有相同的元素,如果不跳过得到的结果中有相同的
}
}
idx1++;
while (idx1 < nums.size()-2 && nums[idx1] == nums[idx1-1]) idx1++;//跳过所有相同的元素,如果不跳过得到的结果中有相同的
}
return rst;
}
};