给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例1
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解法一
自己的暴力解法,即用三个循环遍历出所有的可能的三数相加,但是不出意外的超出了力扣的时间限制。
def threeSum1(nums):
"""暴力求解法,超出时间限制"""
n = len(nums)
# 如果数组元素小于3个,直接返回空数组
if n < 3: return []
ans = [] # 记录结果
for i in range(n):
for j in range(i + 1, n):
for x in range(n):
# 当为三个不同数的时候,分别表示出来
# 然后相加,判断是否为0
if x != i and x != j \
and nums[i] + nums[j] + nums[x] == 0:
ans.append([nums[i], nums[j], nums[x]])
arr = [] # 记录集合
new_ans = [] # 记录去重后的数组
# 遍历数组去重
for i, lt in enumerate(ans):
s = set()
s.update(lt) # 将每个数组转为集合
if s not in arr:
# 如果该集合不在数组里面,那么说明是新的三个数
# 那么将该集合放到数组里面,并且添加数组到返回的new_ans里面
arr.append(s)
new_ans.append(lt)
return new_ans
解法二
还是自己的解法,虽然不是最暴力的,但还是没通过LeetCode的时间限制。想看最优解的,还是直接看解法三。
主要思想:将数组分成正数,负数和零三组数据,然后再分别遍历正数和负数数组,找出所有可能的三数之和。[0, 0, 0] [0, 正, 负] [正, 正, 负] [正, 负, 负]总共无非也就这四种情况。
def threeSum2(nums):
positive_dict = {} # 保存正数的字典
negative_dict = {} # 保存负数的字典
zero_dict = {0: 0} # 保存0的字典,默认0个
ans = [] # 记录结果
for i, num in enumerate(nums):
if num == 0:
zero_dict[0] += 1
elif num > 0:
if num not in positive_dict:
positive_dict[num] = 1
else:
positive_dict[num] += 1
elif num < 0:
if num not in negative_dict:
negative_dict[num] = 1
else:
negative_dict[num] += 1
positive_len = len(positive_dict.keys())
negative_len = len(negative_dict.keys())
for i in range(positive_len):
pt1 = list(positive_dict.keys())[i]
# 至少有一个0的情况, 就肯定有一对相反数: [1, 0, -1]
if zero_dict[0] >= 1 and -pt1 in negative_dict:
ans.append([pt1, -pt1, 0])
# 没有0的情况,两个正数相同,一个负数: [1, 1, -2]
if positive_dict[pt1] >= 2 and -pt1*2 in negative_dict:
ans.append([pt1, pt1, -pt1*2])
# 没有0的情况,两个正数不同,一个负数: [1, 2, -3]
for j in range(i+1, positive_len):
pt2 = list(positive_dict.keys())[j]
if -(pt1 + pt2) in negative_dict:
ans.append([pt1, pt2, -(pt1 + pt2)])
for i in range(negative_len):
ng1 = list(negative_dict.keys())[i]
# 没有0的情况,两个负数相同,一个正数: [-1, -1, 2]
if negative_dict[ng1] >= 2 and -ng1*2 in positive_dict:
ans.append([ng1, ng1, -ng1*2])
# 没有0的情况,两个负数不同,一个正数: [-1, -2, 3]
for j in range(i+1, negative_len):
ng2 = list(negative_dict.keys())[j]
if -(ng1 + ng2) in positive_dict:
ans.append([ng1, ng2, -(ng1 + ng2)])
# 如果0的个数大于3,还有三个0的情况
if zero_dict[0] >= 3: ans.append([0, 0, 0])
return ans
解法三
参考力扣官方和精选答案,采用排序加双指针的方法。
主要思想:在解法一的三层循环上进行优化,在第一层循环保持不变的情况下,将第二层和第三层循环放到一个循环里面。
- 每次比较
nums[i] + nums[L] + nums[R]三数之和和0的大小,i是第一层循环的数的下标(在第二层循环的时候,是固定值),L和R分别是指向i之后所有元素的左右指针。 - 因为是排序后的数组,所有如果三数之和小于0,那么就肯定是
nums[L]偏小了,所以L向右移动一位 - 同理,有如果三数之和大于0,那么就肯定是
nums[R]偏大了,所以R向左移动一位
def threeSum3(nums):
"""排序+双指针"""
n = len(nums)
res = [] # 记录返回的数组
if not nums or n < 3: return []
nums.sort() # 对数组进行排序
for i in range(n):
# 当 nums[i] > 0时直接返回结果:因为 nums[i] >= nums[L] >= nums[R] > 0,
# 即3个数字都大于0 ,在此固定指针 i 之后不可能再找到结果了
if nums[i] > 0: return res
if i > 0 and nums[i] == nums[i - 1]: continue # 跳过重复元素
L, R = i + 1, n - 1 # 用左右指针分别指向固定指针 i 之后元素的左右两侧
while L < R:
if nums[i] + nums[L] + nums[R] == 0:
res.append([nums[i], nums[L], nums[R]]) # 当满足三数之和等于0,就记录下来
while L < R and nums[L] == nums[L + 1]: L += 1 # 去除左边的重复元素
while L < R and nums[R] == nums[R - 1]: R -= 1 # 去除右边的重复元素
L += 1 # 满足了一组三数之和等于0之后,左右指针就向中间都移动一位,寻找新的组合
R -= 1
elif nums[i] + nums[L] + nums[R] > 0:
R -= 1 # 如果三数之和大于0,那么就是右边的元素偏大,所以向左移动一位
else:
L += 1 # 反之,# 如果三数之和小于0,那么就是左边的元素偏小,所以向右移动一位
return res
复杂度分析
-
时间复杂度: ,数组排序,遍历数组,双指针遍历,总体,所以时间复杂度为
-
空间复杂度: