一、前置知识点:排序 + 双指针
1. 数组排序
- 作用:将无序数组变为有序,是使用对向双指针的前提。
- 优势:排序后可以利用有序性,通过指针移动快速缩小查找范围,避免暴力枚举的 O (n³) 复杂度。
- Python 实现:
nums.sort()原地排序,时间复杂度 O (n log n)。
2. 对向双指针(左右指针)
-
核心思想:
-
固定一个基准元素,在其右侧子数组中用
left和right指针向中间逼近。 -
根据三数之和与目标值的大小关系,移动指针:
- 和 > 目标值 →
right左移(减小和) - 和 < 目标值 →
left右移(增大和) - 和 = 目标值 → 记录结果,并跳过重复值
-
-
时间复杂度:O (n²)(排序 O (n log n) + 双指针遍历 O (n²))
-
空间复杂度:O (1)(原地排序,仅用常数额外空间)
3. 去重技巧
- 基准元素去重:若当前元素与前一个相同,直接跳过,避免生成重复三元组。
- 结果元素去重:找到符合条件的三元组后,继续移动
left和right指针,跳过所有与当前结果相同的元素。
二、经典算法题:三数之和(LeetCode 15)
题目描述
给定一个整数数组 nums,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k,同时满足 nums[i] + nums[j] + nums[k] = 0。请你返回所有和为 0 且不重复的三元组。
示例:
- 输入:
nums = [-1, 0, 1, 2, -1, -4] - 输出:
[[-1, -1, 2], [-1, 0, 1]]
最优解法:排序 + 双指针
核心思路
- 固定一个数:遍历数组,将每个元素
nums[i]作为三元组的第一个数。 - 双指针找另外两个数:在
i之后的子数组中,用left = i+1和right = len(nums)-1指针寻找和为-nums[i]的两个数。 - 跳过重复值:在遍历和查找过程中,主动跳过重复元素,保证结果不重复。
from typing import List
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
res = [] nums.sort() # 先排序,方便双指针查找
n = len(nums)
for i in range(n - 2): # 跳过基准元素的重复值
if i > 0 and nums[i] == nums[i-1]:
continue
# 如果第一个数就>0,后面不可能和为0,直接break
if nums[i] > 0:
break
left = i + 1
right = n - 1
while left < right:
total = nums[i] + nums[left] + nums[right]
if total < 0:
# 和太小,左指针右移
left += 1
elif total > 0:
# 和太大,右指针左移
right -= 1
else:
# 找到一个三元组
res.append([nums[i], nums[left], nums[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
return res
代码执行示例
以 nums = [-1, 0, 1, 2, -1, -4] 为例:
-
排序后:
[-4, -1, -1, 0, 1, 2] -
i=0(nums[i]=-4):left=1,right=5→ 和为-4 + (-1) + 2 = -3 < 0→left++- 最终无法找到和为
4的组合,无结果
-
i=1(nums[i]=-1):left=2,right=5→ 和为-1 + (-1) + 2 = 0→ 记录[-1, -1, 2]- 跳过重复后
left=3,right=4→ 和为-1 + 0 + 1 = 0→ 记录[-1, 0, 1]
-
i=2:与i=1重复,直接跳过 -
i=3(nums[i]=0):0 > 0不成立,但后续元素均 >0,无法和为 0,循环结束 -
最终结果:
[[-1, -1, 2], [-1, 0, 1]]
三、关键知识点总结
1. 时间复杂度分析
- 排序:O(n log n)
- 双指针遍历:外层循环 O (n),内层双指针遍历 O (n),总计 O (n²)
- 整体:O(n log n) + O(n²) = O(n²) ,远优于暴力法 O (n³)
2. 去重逻辑详解
- 基准去重:
if i > 0 and nums[i] == nums[i-1]→ 避免以相同数字作为第一个数,生成重复三元组。 - 结果去重:找到和为 0 的三元组后,继续移动
left和right指针,跳过所有与当前left/right相同的元素,避免同一组数字生成多次结果。
3. 剪枝优化
- 提前终止:当
nums[i] > 0时,由于数组已排序,后续元素均 ≥nums[i],三数之和必然 > 0,直接break终止循环。
四、双指针法在 n 数之和中的通用思路
| 题目 | 核心思路 | 复杂度 |
|---|---|---|
| 两数之和 | 哈希表 / 排序 + 双指针 | O(n) / O(n log n) |
| 三数之和 | 固定一个数 + 双指针 | O(n²) |
| 四数之和 | 固定两个数 + 双指针 | O(n³) |
| n 数之和 | 固定 n-2 个数 + 双指针 | O(n^(n-1)) |