题目描述
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例:
给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
解题思路一
参考两数之和的解法,多指针法,两数之和需要左指针和右指针,所以可以合理的推测,三数之和需要用到三个指针。
为了能用上两数之和的结论,首先要固定一个元素,这样问题就变成了两数之和,然后在利用双指针法求解即可。
(1)遍历整个数组,当前位置k
(2)固定位置k在数组中的值,然后按照两数之和的解法,移动左指针右指针,使其对应数组的值相加等于k在数组中的值的相反数。
(3)去重,保证当前三个数字的组合在最后的结果集中仅出现一次
更多细节结合代码展开:
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
res = []
nums.sort() # 排序是必须的,决定了移动左右指针时能不能输出正确的组合
for k in range(len(nums)-2):
# nums是从小到大排序的,所以如果第一个元素大于0,其余的元素一定是大于0的,
# 此时没有继续查找的必要,直接跳出循环
if nums[k] > 0:
break
# 去重,如果当前固定k指针对应值和它前面一个相同,则跳过
if k > 0 and nums[k] == nums[k-1]:continue
# 固定k指针后,设置左指针和右指针,和两数之和的设置方式相同,不同的是
# 左指针的起点
i = k + 1
j = len(nums) - 1
# 基本与两数之和的处理过程相同
# 不同之处是增加去重操作
while i < j :
# 一个有效组合,固定指针k对应的值 + 左指针i对应的值 + 右指针j对应的值
# curr 代表当前组合相加得到的和
curr = nums[k] + nums[i] + nums[j]
print(curr)
if curr == 0:
temp = [nums[k],nums[i],nums[j]]
res.append(temp)
i += 1
j -= 1
# 添加一组满足条件的组合后,左指针前右移动一位,
# 右指针向左移动一位
# 去重,左右指针移动之后和上一个位置的值如果相同,则要继续移动
# 保证每次添加到结果集中的组合都是不同的
while i < j and nums[i] == nums[i - 1]: i += 1
while i < j and nums[j] == nums[j + 1]: j -= 1
elif curr > 0:
j -= 1
while i < j and nums[j] == nums[j + 1]: j -= 1
else:
i += 1
while i < j and nums[i] == nums[i - 1]: i += 1
return res
解题思路二
依然是参考两数之和的解法,利用字典(哈希表)进行快速查找。
字典的 key-value都是一一对应的,所以需要先固定两个数,然后判断用0减去固定的两个数,判断差是否在字典中,去重输出结果集。
(1)建字典。
数组中三个数字相加等于0
a + b + c = 0
那么一定有
a + b = -c
then:
hash_map = {-x:i for i,x in enumerate(nums)}
(2)固定两个数,动态检测是否有和这两个数能够成组的第三个数。
(3)去重
结合代码进一步说明
class Solution:
def threeSum(self,nums: List[int]) -> List[List[int]]:
res = []
nums.sort()
hash_map = {-x:i for i,x in enumerate(nums)}
# 这个需要额外增加一个用于去重判断的res_hash,
# 如果直接用 in 判断,则会超时
res_hash = {}
# 异常判断
if len(nums) < 3:
return res
for i,first in enumerate(nums):
# 先固定第一个数,如果从小到大排序后的第一个数大于0,
# 那么其余数必然大于0,直接跳出循环
if first > 0:
break
# 去重,如果下一个位置固定的第一个数和上一个位置一样,则跳过
# 从后面的元素开始
if i > 0 and first == nums[i-1]:
continue
# 固定第二个数
for j,second in enumerate(nums[i+1:]):
# curr表示当前固定的两个数的和
curr = first + second
# 判断curr是否在hash_map中
if curr in hash_map:
third_index = hash_map[curr]
# 去重,如果此时第三个数的索引和第一个固定位置重叠,或者
# 和第二个固定位置重叠,则跳过
# 为什么第二个固定位置不是j?
# j 是 0 开始的相对位置,对应到原始数组中的索引是 i+1+j
if third_index == i or third_index == i+1+j:
continue
# 排序,是为了保证结果集中的组合都是独一无二的。
row = sorted([first,second,nums[third_index])
# 如果直接用:
# if row not in res:
# res.append(row)
# return res
# 提交后会超时,这是因为列表的in操作速度比字典的in判断速度慢得多,
# 尤其是列表中元素较多时。详情可以看下这两个in的原理
key = ''.join([str(x) for x in row])
if key not in res_hash:
res.append(row)
res_hash[key] = True
return res
思路一解法执行用时/内存消耗 | 思路二解法执行用时/内存消耗 |
2660ms/16.8MB | 504ms/18.2MB |
得益于字典的快速查找,思路二解法比思路一快了将近5倍,内存消耗比思路一多了差不多2MB多一点。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/3sum