# 三数之和 (3Sum) LeetCode 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 ``` ## 解题思路 ### 核心思想 **排序 + 双指针**:先排序,固定第一个数,再用双指针找后两个数。 ### 算法流程 1. **排序数组**:便于去重和双指针操作 2. **遍历第一个数**: - 跳过重复元素 - 如果当前数 > 0,直接退出(后面都 > 0) 3. **双指针找后两个数**: - left = i + 1, right = len(nums) - 1 - 根据 sum 与 0 的关系移动指针 - 跳过重复元素 ### 复杂度分析 - **时间复杂度**:O(n²),排序 O(n log n) + 双指针 O(n²) - **空间复杂度**:O(1),不考虑结果存储 --- ## Go 解法 ```go func threeSum(nums []int) [][]int { result := [][]int{} n := len(nums) // 排序 sort.Ints(nums) for i := 0; i < n-2; i++ { // 去重:跳过重复的第一个数 if i > 0 && nums[i] == nums[i-1] { continue } // 优化:如果最小数 > 0,后面不可能有解 if nums[i] > 0 { break } // 双指针 left, right := i+1, n-1 for left < right { sum := nums[i] + nums[left] + nums[right] if sum == 0 { result = append(result, []int{nums[i], nums[left], nums[right]}) // 去重:跳过重复的 left for left < right && nums[left] == nums[left+1] { left++ } // 去重:跳过重复的 right for left < right && nums[right] == nums[right-1] { right-- } // 移动指针 left++ right-- } else if sum < 0 { // 和太小,left 向右移 left++ } else { // 和太大,right 向左移 right-- } } } return result } ``` ### Go 代码要点 1. `sort.Ints()` 排序 2. `append()` 添加结果 3. 多重去重逻辑 --- ## Java 解法 ```java class Solution { public List> threeSum(int[] nums) { List> result = new ArrayList<>(); Arrays.sort(nums); for (int i = 0; i < nums.length - 2; i++) { // 去重:跳过重复的第一个数 if (i > 0 && nums[i] == nums[i - 1]) { continue; } // 优化:如果最小数 > 0,后面不可能有解 if (nums[i] > 0) { break; } // 双指针 int left = i + 1, right = nums.length - 1; while (left < right) { int sum = nums[i] + nums[left] + nums[right]; if (sum == 0) { result.add(Arrays.asList(nums[i], nums[left], nums[right])); // 去重:跳过重复的 left while (left < right && nums[left] == nums[left + 1]) { left++; } // 去重:跳过重复的 right while (left < right && nums[right] == nums[right - 1]) { right--; } left++; right--; } else if (sum < 0) { left++; } else { right--; } } } return result; } } ``` ### Java 代码要点 1. `Arrays.sort()` 排序 2. `Arrays.asList()` 创建列表 3. `ArrayList` 存储结果 --- ## 图解过程 ``` 数组: [-4, -1, -1, 0, 1, 2] ↑ ↑ ↑ i left right 第一轮: i = 0, nums[i] = -4 left = 1, right = 5 sum = -4 + (-1) + 2 = -3 < 0 left++ left = 2, right = 5 sum = -4 + (-1) + 2 = -3 < 0 left++ left = 3, right = 5 sum = -4 + 0 + 2 = -2 < 0 left++ left = 4, right = 5 sum = -4 + 1 + 2 = -1 < 0 left++ left >= right, 退出 第二轮: i = 1, nums[i] = -1 left = 2, right = 5 sum = -1 + (-1) + 2 = 0 ✓ 结果: [-1, -1, 2] left = 3, right = 4 sum = -1 + 0 + 1 = 0 ✓ 结果: [-1, 0, 1] 第三轮: i = 2, nums[i] = -1 (重复,跳过) 第四轮: i = 3, nums[i] = 0 > 0, 退出 最终结果: [[-1,-1,2], [-1,0,1]] ``` --- ## 进阶问题 ### Q1: 如果是四数之和? **方法**:在三层循环 + 双指针,时间 O(n³) ```go func fourSum(nums []int, target int) [][]int { result := [][]int{} sort.Ints(nums) n := len(nums) for i := 0; i < n-3; i++ { if i > 0 && nums[i] == nums[i-1] { continue } for j := i + 1; j < n-2; j++ { if j > i+1 && nums[j] == nums[j-1] { continue } left, right := j+1, n-1 for left < right { sum := nums[i] + nums[j] + nums[left] + nums[right] if sum == target { result = append(result, []int{nums[i], nums[j], nums[left], nums[right]}) for left < right && nums[left] == nums[left+1] { left++ } for left < right && nums[right] == nums[right-1] { right-- } left++ right-- } else if sum < target { left++ } else { right-- } } } } return result } ``` ### Q2: 如果数组很大,如何优化? **优化**: 1. 提前终止:`nums[i] * 3 > target`(正数情况) 2. 二分查找:确定第二个数后,二分查找后两个 3. 哈希表:空间换时间 --- ## P7 加分项 ### 深度理解 - **排序的作用**:去重 + 双指针基础 - **双指针原理**:利用有序性,单向移动 - **去重策略**:多处去重,确保结果唯一 ### 实战扩展 - **大数据场景**:外部排序 + 分段处理 - **分布式场景**:MapReduce 框架 - **业务场景**:推荐系统、用户画像匹配 ### 变形题目 1. [16. 最接近的三数之和](https://leetcode.cn/problems/3sum-closest/) 2. [18. 四数之和](https://leetcode.cn/problems/4sum/) 3. [259. 较小的三数之和](https://leetcode.cn/problems/3sum-smaller/) --- ## 总结 这道题的核心是: 1. **排序**:为双指针和去重创造条件 2. **固定一个数**:将问题转化为两数之和 3. **双指针**:根据 sum 与 target 的关系移动指针 4. **多重去重**:i、left、right 都要跳过重复元素 **易错点**: - 忘记排序 - 去重逻辑不完整 - left 和 right 的移动条件 - 优化提前终止的条件 **最优解法**:排序 + 双指针,时间 O(n²),空间 O(1)