题意描述:
给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例:
给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
解题思路:
Alice: 这个三重循环肯定会超时吧。
Bob: 那肯定的,O(n^2)
都够呛,而且去重应该没那么简单。如果先把所有的答案都求出来,然后再去重可能会不行哦。求解的时候有重复计算,去重的时候还要再附加计算,可能会超时啊。
Alice: emmm, 有数组不能先排个序吗,然后再分析看看,要求 a + b + c == 0
那肯定得有正有负吧。
Bob: 不一定哦,万一是 三个零 [0, 0, 0]
呢。
Alice: 那就除了 三个零的情况,至少得有一个正数,至少得有一个负数。
Bob: 我们可以在外层遍历整个排序后的数组, 然后指定最外层遍历的是三元组中的最小值,这样就只遍历 负数 的部分就可以了。
Alice: 对对对,如果三元组中最小值是正数这样的三元组是不成立的,如果最小值是零,刚才已经处理过了。
Bob: 然后我们用双指针来 寻找 三元组中的 中间值和 最大值, 中间值一定要小于等于最大值。
Alice: 也就是说 两个指针 left < right
是一定成立的,然后还有去重的问题,无论是最小值,中间值,最大值的都要去重。
Bob: 对,最小值用 pre
记录上一次访问的值,然后比较呗,如果再遇到就跳过呗。
Alice: 然后就是 left
和 right
, 如果 nums[minIndex] + nums[left] + nums[right] == 0
,然后left
和 right
要向中间移动,一直移动到 两个新的元素为止。
Bob: 如果 nums[minIndex] + nums[left] + nums[right] < 0
就 left
右移,否则 right
左移,应该就可以了。
Alice; 那应该就没错了,最小值,中间值,最大值的去重都考虑了,时间复杂度是O(n^2)
,空间复杂度应该是O(1)
,还不错。
Bob: 😎😎
代码:
Python 方法一: 排序 + 双指针。
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
ans = []
if len(nums) < 3:
return ans
else:
nums.sort()
positiveIndex = -1
zeroCnt = 0
for x in range(len(nums)):
if nums[x] == 0:
zeroCnt += 1
elif nums[x] > 0:
positiveIndex = x
break
#print(nums)
#print(zeroCnt)
if positiveIndex == -1:
# 没有正数
if zeroCnt >= 3:
return [[0,0,0]]
else:
return []
pre = nums[0] + 1
# pre 初始值不等于 nums[0] 即可
for x in range(len(nums)):
# 遍历 所有可能的三元组中最小的那一个
if nums[x] > 0:
# nums[x] > 0, x 后的所有数字都是正数
continue
elif nums[x] < 0:
if nums[x] == pre:
# 去重, 两个最小值
continue
else:
# 寻找满足条件的三元组
pre = nums[x]
positiveOne = len(nums)-1
# positiveOne 是三元组中最大的 元素的下标
anotherOne = x + 1
while anotherOne < positiveOne:
if nums[x] + nums[anotherOne] + nums[positiveOne] == 0:
ans.append([nums[x], nums[anotherOne], nums[positiveOne]])
# 去重
while positiveOne > anotherOne and nums[positiveOne] == nums[positiveOne-1]:
positiveOne -= 1
while anotherOne < positiveOne and nums[anotherOne] == nums[anotherOne+1]:
anotherOne += 1
# 尝试新的答案
positiveOne -= 1
anotherOne += 1
elif nums[x] + nums[anotherOne] + nums[positiveOne] < 0:
anotherOne += 1
else:
positiveOne -= 1
else:
# nums[x] == 0, 且 0 是三元组中的最小值
if zeroCnt >= 3:
# 最后一组三元组
ans.append([0,0,0])
break
return ans
Java 方法一: 排序 + 双指针。
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> ans = new ArrayList(); // ans 存储答案
if(nums.length < 3){ // 不可能有满足条件的三元组,直接返回
return ans;
}else{
Arrays.sort(nums); // 排个序
int zeroCnt = 0;
int firstPositiveIndex = -1; // 统计下数组中零的个数 和 第一个正数出现的下标
for(int i=0; i<nums.length; ++i){
if(firstPositiveIndex == -1 && nums[i] > 0){
firstPositiveIndex = i;
break;
}
if(nums[i] == 0){
zeroCnt += 1;
}
}
if(firstPositiveIndex == -1){ // 一个正数也没有就可以直接返回了
// nothing, 直接到最后加零走人
}else{
int pre = nums[0] + 1; // 去重用,pre 初始值和 nums[0] 不同即可
for(int minIndex = 0; minIndex < nums.length; ++minIndex){ // 以三元组中最小的元素开始搜索
if(nums[minIndex] >= 0){
break; // 后面是没有答案的,可以直接退出循环了
}else{
if(pre == nums[minIndex]){ // 去重,跳过这个最小元素
continue;
}
pre = nums[minIndex];
int left = minIndex + 1;
int right = nums.length - 1; // right 指向三元组最大的一个数,必须是正数
while(left < right){
if(nums[minIndex] + nums[left] + nums[right] == 0){
List<Integer> tmp = new ArrayList();
tmp.add(nums[minIndex]);
tmp.add(nums[left]);
tmp.add(nums[right]);
ans.add(tmp);
while(right > left && nums[right] == nums[right-1]){
right -= 1; // 去重,最大值不能重复
}
while(left < right && nums[left] == nums[left+1]){
left += 1; // 去重,中间值不能重复
}
left += 1; // 一直到 left 和 right 指向下两个不一样的值
right -= 1;
}else if(nums[minIndex] + nums[left] + nums[right] < 0){
left += 1;
}else{
right -= 1;
}
}
}
}
}
if(zeroCnt >= 3){
List<Integer> zeroAns = new ArrayList(); // 准备三个零的答案
zeroAns.add(0);
zeroAns.add(0);
zeroAns.add(0);
ans.add(zeroAns);
}
return ans;
}
}
}
易错点:
- 一些测试用例:
[-1,0,1,2,-1,-4]
[-1,-2,-3,0,0,0]
[-4,2,2,2,1,3,0,0,0,4]
[-2,-2,-4,0,0,0,1,1,2,2,4]
[0,2,3,3]
[-1,-2]
[2,3,4,5]
- 答案:
[[-1,-1,2],[-1,0,1]]
[[0,0,0]]
[[-4,0,4],[-4,1,3],[-4,2,2],[0,0,0]]
[[-4,0,4],[-4,2,2],[-2,-2,4],[-2,0,2],[-2,1,1],[0,0,0]]
[]
[]
[]
总结:
- 这题怎么着也能记住个几年吧。🤣🤣🤣
来源:CSDN
作者:花花生
链接:https://blog.csdn.net/A_D_E/article/details/104111308