题目
给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
思路
要求的是a+b+c=0 其实就是要求a+b=-c,那么问题可以转化为依次遍历数组元素c,然后在剩下的数中做两数之和为-c的问题。
问题在于如何简化算法以及优化复杂度。 1.首先可以先排序(O(nlogn)),这样保证数组有序之后可以利用大小关系判断。
2.设置两个指针left、right,分别从左边以及右边向中间遍历,如果找到a+b+c==0,那么可以将这个答案加入到答案集里 如果a+b+c<0,此时固定的是c,说明a+b太小了,因此left+=1;如果a+b+c>0,此时a+b过大,因此right-=1
3.去重,这一步则是利用了有序性,如果两个数相同,那他们在数组的位置一定是相邻的(连着几个数相同也是可能的),因此 去重的操作就能简单遍历一下相邻的是否相同即可。由于数组有序性使得去重这一步很简单,因此也可以看出第一步的作用。
此外还有一些小细节的地方,比如说当遍历到c>0的时候,由于之后的数都是正数,那三数之和一定大于0,就没必要继续遍历c了(因为 继续向后遍历c只会更大,那之后的数加起来一定大于0); 或者固定c,如果c及其后面连着两个数a,b,他们的和已经大于0了,就没必要进行下一步的操作,此时遍历下一个c; 同理,如果c和数组最后两个数的和仍然小于0,也没必要进行下一步操作。
代码
//最大答案数 #define MAX_ANS_NUM 1000000 int comp(const void* a, const void* b) { return *((int*)a) - *((int*)b); } int** threeSum(int* nums, int numsSize, int* returnSize, int** returnColumnSizes) { int** result = (int**)calloc(MAX_ANS_NUM, sizeof(int*)); *returnColumnSizes = (int*)calloc(MAX_ANS_NUM, sizeof(int)); *returnSize = 0; if (numsSize < 3) return result; //先将入参排序 qsort(nums, numsSize, sizeof(int), comp); //排除极端异常情况: if (nums[0] + nums[1] + nums[2] > 0) return result; // 最小三个数相加都大于0直接无解 if (nums[numsSize - 1] + nums[numsSize - 2] + nums[numsSize - 3] < 0) return result; //最大三个数相加都小于0直接无解 int left, right, anscount; anscount = 0; for (int i = 0; i < numsSize - 2; i++) { if (nums[i] + nums[i + 1] + nums[i + 2]) { if (nums[i] + nums[numsSize - 1] + nums[numsSize -2] < 0) continue; // 剪枝优化无效判断 最小的加上 最大的两个数都小于0继续找下一轮 } if (i > 0 && nums[i] == nums[i - 1]) { continue; // 重复的判断 } left = i + 1; right = numsSize - 1; while (left < right) { if (nums[left] + nums[i] + nums[right] == 0) { result[anscount] = (int*)calloc(3, sizeof(int)); result[anscount][0] = nums[left]; result[anscount][1] = nums[i]; result[anscount][2] = nums[right]; (*returnColumnSizes)[anscount] = 3; anscount++; while(left + 1 < right && nums[left] == nums[left + 1]) { left++; } left++; while (left < right - 1 && nums[right] == nums[right - 1]) { right--; } right--; } else if (nums[left] + nums[i] + nums[right] < 0) { left++; } else if (nums[left] + nums[i] + nums[right] > 0) { right--; } } } *returnSize = anscount; return result; }