题目:三数之和
内容:
给定一个包含 n 个整数的数组 nums
,判断 nums
中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
思路:
题目实现可分为两个步骤,分别是(1)寻找三个满足条件的元素(2)去重复
对于第一个小问题,首先考虑三个for循环,直接寻找
1.第一个数字(num1)选取范围1~n
2.第二个数字(num2)选取范围num1+1~n
3.第三个数字(num3)选取范围num2+1~n
代码如下:
1 //寻找三数之和为0的数 C++ 2 vector<int> vec(3); 3 unsigned int num1 = 0, num2 = 0, num3 = 0; 4 for (num1; num1 < nums.size() - 2; num1++){ 5 for (num2 = num1 + 1; num2 < nums.size() - 1; num2++){ 6 for (num3 = num2 + 1; num3 < nums.size(); num3++){ 7 if (nums[num1] + nums[num2] + nums[num3] == 0){ 8 vec[0] = nums[num1]; 9 vec[1] = nums[num2]; 10 vec[2] = nums[num3]; 11 vecs.push_back(vec); 12 } 13 } 14 } 15 }
第二个小问题去重复
C++ 的STL提供有去重算法unique,直接去重即可
1.在寻找满足条件的三个数字之前要先排序,防止相同的vec因为内部元素顺序不同去不了重复,如[1,2,3]和[2,1,3]会判定为不重复
2.对于vecs结果进行去重,去重之前一定要再次排序,因为unique函数只是比较相邻的两个元素是否重复,如果重复就将重复的放到尾部,如果不限排序,对于vecs[[1,2,3],[0,0,0],[1,2,3]],因为相邻的元素都不想等([1,2,3]≠[0,0,0]),系统会去重失败
去重代码如下:
1 //先排序,方便去重 2 sort(nums.begin(), nums.end()); 3 //寻找三个满足条件的数字代码省略。。。。 4 //去重 5 sort(vecs.begin(), vecs.end()); 6 vecs.erase(unique(vecs.begin(), vecs.end()), vecs.end()); 7 8 return vecs;
此外,还要考虑异常的情况,当传入的数组长度小于3时,无法给出满足条件的解,应返回空的容器
完整代码如下:
1 vector<vector<int>> threeSum(vector<int>& nums) { 2 vector<vector<int>> vecs; 3 //异常判断 4 if (nums.size() < 3) 5 return vecs; 6 7 //先排序,方便去重 8 sort(nums.begin(), nums.end()); 9 10 //寻找三数之和为0的数 11 vector<int> vec(3); 12 unsigned int num1 = 0, num2 = 0, num3 = 0; 13 for (num1; num1 < nums.size() - 2; num1++){ 14 for (num2 = num1 + 1; num2 < nums.size() - 1; num2++){ 15 for (num3 = num2 + 1; num3 < nums.size(); num3++){ 16 if (nums[num1] + nums[num2] + nums[num3] == 0){ 17 vec[0] = nums[num1]; 18 vec[1] = nums[num2]; 19 vec[2] = nums[num3]; 20 vecs.push_back(vec); 21 } 22 } 23 } 24 } 25 26 //去重 27 sort(vecs.begin(), vecs.end()); 28 vecs.erase(unique(vecs.begin(), vecs.end()), vecs.end()); 29 30 return vecs; 31 }
对于较短的输入,能给出合适的结果,然后对于巨长的数组,系统提示运算时间过长,因此需要优化代码。
根据题目所给的条件,可以看出三个数字之和是定值,因此,当我们选取第一个数字num1后,问题变为寻找和为0-num1的两个数字。
可以观察到,因为数组是有序的,我们可以设置两个指针从两边同时选取
1 int targetSum = 0 - nums[num1]; 2 while (pLeft < pRight ){ 3 int sum = nums[pLeft] + nums[pRight]; 4 if (sum > targetSum || nums[pRight] == nums[pRight-1]){ 5 pRight--; 6 } 7 else if (sum < targetSum || nums[pLeft] == nums[pLeft - 1]){ 8 pLeft++; 9 } 10 else{ 11 vec[0] = nums[num1]; 12 vec[1] = nums[pLeft]; 13 vec[2] = nums[pRight]; 14 vecs.push_back(vec); 15 pLeft++; 16 pRight--; 17 } 18 }
也因为数组是排序的,为了如果我们选取的第一个数子大于0,则后两个必然大于0,可以跳出循环
对于重复的数字,我们可以选择跳过
完整代码如下:
1 vector<vector<int>> threeSum(vector<int>& nums) { 2 vector<vector<int>> vecs; 3 //异常判断 4 if (nums.size() < 3) 5 return vecs; 6 7 //先排序,方便去重 8 sort(nums.begin(), nums.end()); 9 10 //寻找三数之和为0的数 11 vector<int> vec(3); 12 unsigned int num1 = 0, num2 = 0, num3 = 0; 13 for (num1; num1 < nums.size() - 2; num1++){ 14 if (nums[num1] * 3 > 0)//数组是从小到大排序 15 break; 16 if (num1 != 0 && nums[num1] == nums[num1 - 1]) 17 continue; 18 19 int pLeft, pRight; 20 pLeft = num1+1; 21 pRight = nums.size() - 1; 22 23 int targetSum = 0 - nums[num1]; 24 while (pLeft < pRight ){ 25 int sum = nums[pLeft] + nums[pRight]; 26 if (sum > targetSum || nums[pRight] == nums[pRight-1]){ 27 pRight--; 28 } 29 else if (sum < targetSum || nums[pLeft] == nums[pLeft - 1]){ 30 pLeft++; 31 } 32 else{ 33 vec[0] = nums[num1]; 34 vec[1] = nums[pLeft]; 35 vec[2] = nums[pRight]; 36 vecs.push_back(vec); 37 pLeft++; 38 pRight--; 39 } 40 } 41 } 42 43 //去重 44 sort(vecs.begin(), vecs.end()); 45 vecs.erase(unique(vecs.begin(), vecs.end()), vecs.end()); 46 47 48 return vecs; 49 }
对于复杂测试案例的运行时间是60ms,通过题目!