面试经典150题[029]:三数之和(LeetCode 15)
三数之和(LeetCode 15)
题目链接:三数之和(LeetCode 15)
难度:中等
1. 题目描述
给你一个整数数组 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
- -10^5 <= nums[i] <= 10^5
2. 问题分析
2.1 规律
- 问题本质是找三个不同索引的数之和为0,且结果不重复。
- 暴力O(n^3)会超时,n=3000时太慢。
- 核心问题:如何高效枚举三元组,同时避免重复?
- 由于数组可能有重复元素,需要排序后跳过重复来去重。
2.2 排序 + 双指针思路
我们使用排序 + 双指针算法(时间O(n^2)):
- 先对数组排序(O(n log n)),这样便于去重和使用双指针。
- 遍历每个位置i(从0到n-3),固定nums[i]作为第一个数,目标是找nums[j] + nums[k] == -nums[i],其中j > i, k > j。
- 跳过重复的nums[i](if i > 0 and nums[i] == nums[i-1]: continue),避免重复三元组。
- 对于每个i,设置左指针l = i+1,右指针r = n-1。
- 在[l, r]区间:
- 如果nums[l] + nums[r] == -nums[i],添加三元组[l, i, r]到结果(注意排序后顺序是升序),然后l++,r–,并跳过重复的l和r。
- 如果nums[l] + nums[r] < -nums[i],l++(增大和)。
- 如果nums[l] + nums[r] > -nums[i],r–(减小和)。
- 当l >= r时,结束当前i的搜索。
- 特殊处理全0情况,会自然包含[[0,0,0]]。
这种方式确保每个三元组只被枚举一次,且不重复。
3. 代码实现
Python
class Solution:def threeSum(self, nums):n = len(nums)nums.sort()result = []for i in range(n - 2):# 跳过重复的 iif i > 0 and nums[i] == nums[i - 1]:continuel, r = i + 1, n - 1target = -nums[i]while l < r:total = nums[l] + nums[r]if total == target:result.append([nums[i], nums[l], nums[r]])# 跳过重复的 l 和 rwhile l < r and nums[l] == nums[l + 1]:l += 1while l < r and nums[r] == nums[r - 1]:r -= 1l += 1r -= 1elif total < target:l += 1else:r -= 1return result
C++
class Solution {
public:vector<vector<int>> threeSum(vector<int>& nums) {int n = nums.size();sort(nums.begin(), nums.end());vector<vector<int>> result;for (int i = 0; i < n - 2; ++i) {// 跳过重复的 iif (i > 0 && nums[i] == nums[i - 1]) {continue;}int l = i + 1, r = n - 1;int target = -nums[i];while (l < r) {int total = nums[l] + nums[r];if (total == target) {result.push_back({nums[i], nums[l], nums[r]});// 跳过重复的 l 和 rwhile (l < r && nums[l] == nums[l + 1]) {++l;}while (l < r && nums[r] == nums[r - 1]) {--r;}++l;--r;} else if (total < target) {++l;} else {--r;}}}return result;}
};
4. 复杂度分析
- 时间复杂度:O(n^2),排序O(n log n) + 双指针O(n2),主导是O(n2)
- 空间复杂度:O(1),忽略排序的临时空间和输出结果空间
5. 总结
- 三数之和 + 不重复 → 排序 + 双指针是标准解法
- 核心是固定一数 + 两指针求和,跳过重复确保唯一性
- 可扩展到k数之和(用DFS或更优化)
- 注意边界:n>=3,全0/全非0情况
复习
面试经典150题[014]:加油站(LeetCode 134)