15. 三数之和 Medium
给你一个整数数组 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 。
解题思路
输入:一个整数数组 nums
,元素可能有正有负
输出:找出所有不重复的三元组 [a, b, c]
,满足 a + b + c == 0
本题是典型的 排序 + 相向双指针 问题。
我们可以先对数组进行排序,然后固定第一个数 nums[i]
,接着在 i+1 ~ n-1
范围内使用双指针:
定义两个指针:
left = i + 1
,right = len(nums) - 1
,分别指向第二个数和第三个数的候选位置;对于每个
nums[i]
:- 如果当前数和前一个数相同(
nums[i] == nums[i - 1]
),说明可能会重复三元组,跳过; - 使用双指针遍历
nums[i+1:right]
范围,计算三数之和; - 如果和小于 0,左指针右移(
left += 1
); - 如果和大于 0,右指针左移(
right -= 1
); - 如果和等于 0,加入结果集,并跳过重复的
nums[left]
和nums[right]
;
- 如果当前数和前一个数相同(
当左右指针相遇,当前
i
的处理结束,进入下一轮循环。
本质上就是固定一个数 nums[i]
之后去求剩下的两数之和等于 -num[i]
通过排序 + 去重 + 双指针,本题时间复杂度为 O(n^2)
,是解决「三数之和」的最优常规做法。
代码实现
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums.sort() # 先对数组排序,方便后续使用双指针
ans = [] # 用来存放最终结果
n = len(nums)
for i in range(n - 2): # 枚举第一个数的位置,注意要留出两个数给 j 和 k
x = nums[i]
# 去重:如果当前值和前一个一样,跳过(避免重复三元组)
if i > 0 and nums[i] == nums[i - 1]:
continue
# 剪枝1:当前最小三数之和已经大于0,后面不可能有解,直接退出
if x + nums[i + 1] + nums[i + 2] > 0:
break
# 剪枝2:当前最大三数之和还小于0,不可能凑成0,跳过当前 i
if x + nums[-1] + nums[-2] < 0:
continue
# 双指针从 i+1 到数组末尾开始找满足三数之和为 0 的组合
j = i + 1
k = n - 1
while j < k:
s = x + nums[j] + nums[k] # 当前三数之和
if s < 0:
j += 1 # 总和太小,左指针右移增大总和
elif s > 0:
k -= 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
/**
* @param {number[]} nums
* @return {number[][]}
*/
const threeSum = function(nums) {
nums.sort((a, b) => (a - b));
const ans = [];
for (let i = 0; i < nums.length - 2; i++) {
const x = nums[i];
if (i > 0 && nums[i] == nums[i - 1])
continue
if ((x + nums[i + 1] + nums[i + 2]) > 0)
break
if ((x + nums.at(-1) + nums.at(-2)) < 0)
continue
let j = i + 1;
let k = nums.length - 1;
while (j < k) {
const s = x + nums[j] + nums[k];
if (s > 0) {
k--;
} else if (s < 0) {
j++;
} else {
ans.push([x, nums[j], nums[k]]);
j++;
while (j < k && nums[j] == nums[j - 1]) {
j++;
}
k--;
while (j < k && nums[k] == nums[k + 1]) {
k--;
}
}
}
}
return ans;
};
复杂度分析
时间复杂度:O(n^2)
空间复杂度:O(1)