给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k
且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。注意:答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4] 输出:[[-1,-1,2],[-1,0,1]] 解释: nums[0] +
nums[1] + nums[2] = (-1) + 0 + 1 = 0 。 nums[1] + nums[2] + nums[4] = 0
- 1 + (-1) = 0 。 nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。 不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。 注意,输出的顺序和三元组的顺序并不重要。 示例 2:
输入:nums = [0,1,1] 输出:[] 解释:唯一可能的三元组和不为 0 。 示例 3:
输入:nums = [0,0,0] 输出:[[0,0,0]] 解释:唯一可能的三元组和为 0 。
提示:
3 <= nums.length <= 3000
-105 <= nums[i] <= 105
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums.sort()
# i < j < k
# 答案中不包含重复的三元组
ans = []
n = len(nums)
for i in range(n-2):
x = nums[i]
if i > 0 and x == nums[i-1]:
continue
if x + nums[i+1] + nums[i+2] > 0:
break
if x + nums[n-1] + nums[n-2] < 0:
continue
j = i + 1
k = n - 1
while j < k:
s = x + nums[j] + nums[k]
if s > 0:
k -= 1
elif s < 0:
j += 1
else:
ans.append([x, nums[j],nums[k]])
j += 1
while j < k and nums[j] == nums[j-1]:
j += 1
k -= 1
while k > j and nums[k] == nums[k+1]:
k -= 1
return ans
详细解释
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums.sort() # 对数组进行排序,方便使用双指针法
ans = [] # 存储结果的列表
n = len(nums) # 获取数组的长度
for i in range(n-2): # 遍历数组,选择一个数作为第一个数
x = nums[i]
# 跳过重复的元素,避免结果中有重复的三元组
if i > 0 and x == nums[i-1]:
continue
# 如果当前数和后面两个数的和已经大于 0,说明后面的三元组都不可能满足和为 0,提前结束
if x + nums[i+1] + nums[i+2] > 0:
break
# 如果当前数和最后两个数的和已经小于 0,说明无法通过改变 j 和 k 来达到 0,跳过当前的 i
if x + nums[n-1] + nums[n-2] < 0:
continue
# 使用双指针法,j 从 i+1 开始,k 从 n-1 开始
j = i + 1
k = n - 1
while j < k: # 双指针从两端向中间逼近
s = x + nums[j] + nums[k] # 计算三数之和
if s > 0: # 如果和大于 0,说明需要减小和,右指针向左移动
k -= 1
elif s < 0: # 如果和小于 0,说明需要增大和,左指针向右移动
j += 1
else: # 和为 0,找到了一个符合条件的三元组
ans.append([x, nums[j], nums[k]])
j += 1 # 移动左指针,寻找下一个可能的三元组
# 跳过重复的元素,避免结果中有重复的三元组
while j < k and nums[j] == nums[j-1]:
j += 1
k -= 1 # 移动右指针,寻找下一个可能的三元组
while k > j and nums[k] == nums[k+1]:
k -= 1
return ans
详细解释
1. nums.sort():
• 对数组进行排序,使得我们能够使用双指针技术。排序的目的是为了能够方便地判断是否已经超过目标值,且通过跳过重复元素来优化解法。
2. for i in range(n-2)::
• 外层循环遍历数组中的每个元素 x,作为三元组中的第一个数。n-2 是因为至少需要两个数(j 和 k)来形成三元组。
3. if i > 0 and x == nums[i-1]: continue:
• 这行代码跳过重复的元素。因为数组已排序,如果当前元素 x 与前一个元素相同,说明在之前已经找到过以 x 为第一个数的三元组,避免重复计算。
4. if x + nums[i+1] + nums[i+2] > 0: break:
• 如果当前元素 x 和其后两个最小的元素之和已经大于 0,说明当前 x 无法与后面的数组合成和为零的三元组。因此直接 break,结束循环。
5. if x + nums[n-1] + nums[n-2] < 0: continue:
• 如果当前元素 x 和数组最后两个最大的元素之和已经小于 0,说明 x 和后面的数组合无法达到和为零的条件,跳过当前的 i。
6. 双指针部分:
• j = i + 1 和 k = n - 1:初始化左右指针,j 从 i+1 开始,k 从数组末尾开始。
• while j < k::双指针的核心。指针 j 向右移动,k 向左移动,直到找到符合条件的三元组或指针相遇。
• s = x + nums[j] + nums[k]:计算三数之和。
• if s > 0::如果和大于 0,说明需要减小和,右指针 k 向左移动。
• elif s < 0::如果和小于 0,说明需要增大和,左指针 j 向右移动。
• else::如果和为 0,说明找到了一个符合条件的三元组,添加到结果列表 ans 中。
7. 跳过重复元素:
• while j < k and nums[j] == nums[j-1]: j += 1:如果 nums[j] 与前一个数相同,则跳过,避免重复的三元组。
• while k > j and nums[k] == nums[k+1]: k -= 1:如果 nums[k] 与下一个数相同,则跳过,避免重复的三元组。
8. return ans:
• 返回包含所有不重复三元组的列表。
举例子
假设 nums = [-1, 0, 1, 2, -1, -4]:
1. 排序后: nums = [-4, -1, -1, 0, 1, 2]。
2. 第一轮循环(i = 0):
• 选定第一个数 x = -4。
• j = 1, k = 5,即 nums[j] = -1 和 nums[k] = 2。
• 计算三数之和:s = -4 + (-1) + 2 = -3,和小于
0,左指针 j 向右移动。
• 继续进行类似的操作,直到找到符合条件的三元组。
3. 最终结果: [[-1, -1, 2], [-1, 0, 1]]。
时间复杂度分析
• 时间复杂度: O(n^2),
其中 n 是数组的长度。外层循环遍历 n 个元素,内层双指针遍历最多 n 次,因此总体时间复杂度是 O(n^2)。
• 空间复杂度: O(1),除了存储结果列表 ans 外,使用的额外空间是常数级别。