![](https://img-blog.csdnimg.cn/img_convert/b20220dc1b9c9d4b1cc563c999f8527f.png)
![](https://img-blog.csdnimg.cn/img_convert/96501da834108cc61a4d03d58a6019c0.png)
![](https://img-blog.csdnimg.cn/img_convert/6f695755ac4db8f4aa0637b9dee3059b.png)
1、转换为求解 2 数之和(超时)
a + b + c = 0
等价于b + c = -a
,所以问题就转化为 0001_Two-Sum【S】- 遍历数组,判断其余的数组元素中是否存在两个数的和为当前元素的相反数。
- 由于 0001_Two-Sum【S】的时间复杂度是
O(n)
,算上外层的循环,三数之和的时间复杂度为O(n^2)
,导致此题超时;此外,符合条件的三个数的组合不能重复,多了一步去重的工作,也进一步加大了算法的时间复杂度。
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
sums = []
# jy: 遍历列表中的元素
for i, value in enumerate(nums):
# jy: 从列表 nums 中找出两个值为 -value 的下标 (下标为 i 的值不被考虑)
ls_two_sum = self._two_sum(nums, i, -value)
# jy: 如果 two_sums 这样的值存在 (可能存在多组), 则 3 数之
# 和为 0 的组合存在
if ls_two_sum:
# jy: 将下标转换为相应的数值列表, 并添加到 three_sums 列表中
for two_sum in ls_two_sum:
three_sum = [value] + two_sum
# jy: 判断 sums 组合列表中是否已经包含 three_sum 组合, 如
# 果包含则不再重复加入
if not self._has_three_sum(sums, three_sum):
sums.append(three_sum)
return sums
def _two_sum(self, nums: List[int], current_index: int,
target: int) -> List[List[int]]:
"""
从列表 nums 中找出两个值为 target 的下标, 并确保找到的二元组不重复
(查找过程中忽略下标为 current_index 的值)
"""
mapping = {}
ls_two_sum = []
# jy: 用于记录已获取的数值, 便于去重
set_done = set()
for i, value in enumerate(nums):
if i == current_index:
continue
if target - value in mapping and value not in set_done:
ls_two_sum.append([target - value, value])
set_done.add(value)
set_done.add(target - value)
else:
mapping[value] = i
return ls_two_sum
def _has_three_sum(self, three_sums: List[List[int]],
three_sum: List[int]) -> bool:
"""
判断 three_sums 的组合列表中是否已经包含 three_sum 组合
"""
for sum_ in three_sums:
#if sorted(sum_) == sorted(three_sum):
if set(sum_) == set(three_sum):
return True
return False
nums = [-1, 0, 1, 2, -1, -4]
res = Solution().threeSum(nums)
print(nums, " === ", res)
2、排序 + 问题转换
-
- 外层遍历数组时,如果当前元素值大于 0,则可以直接退出循环,因为数组有序,当前元素后的数必然也是大于 0,不可能存在三数之和为 0
- 如果当前元素的值和前一个元素的值相同,则直接跳过当前元素,因为最终的三元组不能重复(如果当前元素符合条件则在上一轮操作时已包含了这个数)
- 对于外层循环下标为
i
的元素来说,为什么双指针处理只需要查找[k+1, L-1]
区间(L
为数组的长度)的元素即可?因为假设存在满足条件的结果集中有一个元素的下标小于k
,记为k'
,则这个组合已经在外层循环到k'
时就已经找到了这个组合,所以无需再次处理
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
# jy: 对数组进行排序
nums.sort()
sums = []
# jy: 遍历列表中的元素, 找出 nums[i+1: ] 中与 nums[i] 能组合成
# 3 数为 0 的所有组合 (双指针法查找)
for i in range(0, len(nums)-2):
# jy: 3 数之和为 0, 必定以负数开始; 由于数组已经排序, 因此当遍
# 历至大于 0 的数值时, 即可终止 (等于 0 时不能终止, 因为可
# 能后续有两个数为 0, 此时三个 0 也能满足条件)
if nums[i] > 0:
break
# jy: 如果当前数值与前一个数值相同, 则跳过, 避免重复查找; 因为
# 如果当前数值与之后的数值能组成满足要求的一组数, 则在遍历
# 上一个数值时就都已经找到相应的组合了 (不管遍历上一个数值
# 时是否包含该相同的数值)
if i-1 >= 0 and nums[i] == nums[i-1]:
continue
# jy: target 为剩余两数之和
target = - nums[i]
# jy: 剩余两数从 i+1 到 len(nums)-1 的下标中找 (双指针即初始化为这两个下标)
low, high = i+1, len(nums)-1
while low < high:
# jy: 如果两值相等, 则将符合要求的 3 数列表加入 sums 列表组合中
if nums[low] + nums[high] == target:
# jy: 注意需将 target 恢复为原数值
sums.append([-target, nums[low], nums[high]])
# jy: 跳过相同的数值, 避免重复计算
while low < high and nums[low] == nums[low+1]:
low += 1
while low < high and nums[high] == nums[high-1]:
high -= 1
low += 1
high -= 1
elif nums[low] + nums[high] < target:
low += 1
else:
high -= 1
return sums
nums = [-1, 0, 1, 2, -1, -4]
res = Solution().threeSum(nums)
print(nums, " === ", res)