📘 从暴力到优雅:我在「三数之和」中学到的三大算法思维
题目 :LeetCode #15 ------ 三数之和(3Sum)
关键词:双指针|排序去重|固定一数转两数之和|结果收集技巧
一、我的起点:想用暴力,却被现实打脸
刚开始看到「三数之和」,我的第一反应是:
"不就是三层 for 循环吗?遍历所有 i < j < k 的组合,加起来等于 0 就行了。"
但很快我就意识到问题:
- 时间复杂度 O(n³),数据一大就超时;
- 更头疼的是------如何避免重复的三元组 ?比如
[-1, 0, 1]和[0, -1, 1]其实是同一个答案。
我卡住了。直到我学到一个关键思想:
✅ 把"三数之和"转化为"固定一个数 + 两数之和"
二、核心突破:固定一个数,问题降维!
🔑 思路拆解
- 先对数组排序
→ 排序后,相同的数会挨在一起,为去重打下基础 ;同时,双指针才能生效。 - 外层循环固定第一个数
nums[i]
→ 那么问题就变成了:在剩下的子数组中,找两个数,使得它们的和等于-nums[i]。 - 内层用双指针找"两数之和"
left = i + 1right = n - 1- 如果
nums[left] + nums[right] == target→ 找到一组解! - 如果和太小 →
left++ - 如果和太大 →
right--
💡 这一步让我恍然大悟:复杂问题可以通过"固定变量"来降维 。
3Sum → 2Sum,4Sum → 3Sum → 2Sum......这是一个可扩展的模板!
三、结果怎么存?学会用 res = [] + append()
以前我总纠结"怎么返回多个列表",现在我知道了最 Pythonic 的方式:
python
res = [] # 初始化空列表
...
res.append([a, b, c]) # 找到一组就加进去
...
return res
- 简洁、直观、高效;
- 不需要预分配空间,动态增长;
- 最终直接返回这个列表即可。
✅
append是收集多组结果的黄金搭档。
四、最难啃的骨头:去重!为什么不能偷懒?
本题最容易出错的地方,不是找数,而是去重。
我曾想过:"能不能用 set 存 tuple 自动去重?"
技术上可以,但效率低、面试不加分,而且没抓住问题本质。
正确做法:利用"排序后重复值相邻"的特性
去重点 1:跳过重复的 i
python
if i > 0 and nums[i] == nums[i-1]:
continue
→ 避免以相同的数作为起点,产生重复三元组。
去重点 2:找到解后,跳过重复的 left 和 right
python
while left < right and nums[left] == nums[left+1]:
left += 1
while left < right and nums[right] == nums[right-1]:
right -= 1
left += 1
right -= 1
→ 确保下一组 (left, right) 是全新的组合。
🌟 去重的本质不是"事后过滤",而是"事前跳过" 。
在有序数组中,这是最高效的方式。
五、我的完整代码(带注释)
python
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums.sort() # 第一步:排序
res = [] # 初始化结果列表
n = len(nums)
for i in range(n - 2):
# 跳过重复的 i
if i > 0 and nums[i] == nums[i - 1]:
continue
target = -nums[i]
left, right = i + 1, n - 1
while left < right:
s = nums[left] + nums[right]
if s == target:
res.append([nums[i], nums[left], nums[right]])
# 跳过重复的 left 和 right
while left < right and nums[left] == nums[left + 1]:
left += 1
while left < right and nums[right] == nums[right - 1]:
right -= 1
left += 1
right -= 1
elif s < target:
left += 1
else:
right -= 1
return res
六、总结:我带走的三大收获
| 收获 | 说明 |
|---|---|
| 1. 结果收集模式 | 用 res = [] + res.append(...) 动态收集答案 |
| 2. 降维思想 | 固定一个数,将 K 数之和转化为 (K-1) 数之和 |
| 3. 主动去重策略 | 利用排序 + 相邻比较,在过程中跳过重复,而非事后过滤 |