题目来自LeetCode,链接:3sum。具体描述为:给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。需要注意答案中不可以包含重复的三元组。
示例:
给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
与前一篇博客的两数之和相似,一个很容易想到的思路就是暴力法,遍历所有可能的三个数的组合,这样的时间复杂度为 O ( n 3 ) O(n^{3}) O(n3)。然后也可以用到两数之和的想法,就是对于当前遍历到的数,以(0-当前数)作为target,以删去当前数的数组作为待处理数组,问题转变为求两数之和等于target,当然结果可能不止一组,这样做的时间复杂度减为 O ( n 2 ) O(n^{2}) O(n2),但还是会超时,原因在于题目要求不可重复,所以在判断重复上会耗费较多时间。
另一种可行的方法是利用双指针的方法。首先对数组排序,然后对于当前的数,其右边的数组成一个子数组(而且是升序的),只需要在右边搜索两个数之和等于(0-当前数)即可。给定左指针为子数组最左,右指针为子数组最右,左指针对应左数,右指针对应右数,则:
- 如果左数+右数=0-当前数,则[当前数,左数,右数]是一组符合条件的三元组,将左指针右移至不与当前左数相等,右指针左移至不与当前右数相等,这么做是为了避免重复;
- 如果左数+右数<0-当前数,说明需要增大左数,所以左指针右移;
- 否则即左数+右数>0-当前数,说明需要减少右数,所以右指针左移。
最后当前面的流程进行到左指针遇到/超过右指针就结束了,此时要把当前数右移到不与当前数相同位置,同样是为了避免重复。
可以看到这种方法的时间复杂度同样为 O ( n 2 ) O(n^{2}) O(n2),但由于通过避免重复而免去判断重复的过程,从而可以AC。另外,空间复杂度为 O ( 1 ) O(1) O(1),其实就是指针占用空间罢了。
JAVA版代码如下:
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
int numsLen = nums.length;
Arrays.sort(nums);
int i = 0;
List<List<Integer>> result = new ArrayList<>();
while (i < numsLen - 2) {
if (nums[i] > 0) {
return result;
}
int left = i + 1;
int right = numsLen - 1;
int nowNum = nums[i];
while (left < right) {
int leftNum = nums[left];
int rightNum = nums[right];
int sumLR = leftNum + rightNum;
if (sumLR == -nowNum) {
result.add(Arrays.asList(nowNum, leftNum, rightNum));
++left;
while (left < right && nums[left] == leftNum) {
++left;
}
--right;
while (right > left && nums[right] == rightNum) {
--right;
}
}
else if (sumLR < -nowNum) {
++left;
}
else {
--right;
}
}
++i;
while (i < numsLen -2 && nums[i] == nowNum) {
++i;
}
}
return result;
}
}
提交结果如下:
![](https://img-blog.csdnimg.cn/20200226170102777.png)
Python版代码如下:
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums.sort() #先升序排序
length = len(nums)
result = []
i = 0
while i < length - 2: #只遍历到倒数第3个数
if nums[i] > 0: #第一个数已经大于零了,后面不可能再有符合的了
return result
left = i + 1
right = length - 1
now_num = nums[i]
while left < right: #当左指针和右指针相遇就结束一轮循环
left_num = nums[left]
right_num = nums[right]
sumLR = left_num + right_num
if sumLR == -now_num: #三个数的和为0
result.append([now_num,left_num,right_num])
left += 1 #这个时候左指针右移直到所在位置的数不跟其左边的一样
while left < right and left_num == nums[left]:
left += 1
right -= 1 #这个时候右指针左移直到所在位置的数不跟其右边的一样
while right > left and right_num == nums[right]:
right -= 1
elif sumLR < -now_num: #当三个数的和小于零时,左指针右移
left += 1
else: #当三个数的和大于零时,右指针左移
right -= 1
i += 1
while i < length - 2 and nums[i] == now_num: #第一个数移动到不与之前一样为止
i += 1
return result
提交结果如下:
![](https://img-blog.csdnimg.cn/20200226164750823.png)