7.力扣【三数之和】史上最清晰双指针解法!三步搞定,面试必看!
目录
1. 题目解析
1.1 题目描述
15. 三数之和
示例 1:
手写图示解析:
2. 算法原理
2.1 解法一:排序 + 暴力枚举 + 利用 set 去重 —— O(n³)
2.2 解法二:排序 + 双指针 —— O(n²)
步骤:
手写图示:
处理细节问题:
1. 去重:
2. 不漏:
优化点:
3. 编写代码
总结
OJ链接:https://leetcode.cn/problems/3sum/description/
1. 题目解析
1.1 题目描述
15. 三数之和
难度:中等
给你一个整数数组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]。注意:输出的顺序和三元组的顺序并不重要。
手写图示解析:
原数组:[-1, 0, 1, 2, -1, -4]
→ 排序后:[-4, -1, -1, 0, 1, 2]
→ 尝试组合:
[-1, 0, 1]✔️[-1, 2, -1]✔️[0, 1, -1]×
注:对三元组
[−1, 0, 1]和[−1, 2, −1]的判断,以及[0, 1, −1]被划掉,说明答案中不可包含重复的三元组,即使数值相同,顺序不同也视为重复。
2. 算法原理
2.1 解法一:排序 + 暴力枚举 + 利用 set 去重 —— O(n³)
思路:
先对数组排序,然后用三重循环枚举所有可能的三元组,将符合条件的存入Set中去重,最后转为列表返回。
手写图示:
排序后数组:[-4, -1, -1, 0, 1, 2]
→ 找到[-1, 0, 1]两次,说明需要去重。
→ 箭头指向O(n³)表示时间复杂度。
缺点: 时间复杂度高,效率低下。
2.2 解法二:排序 + 双指针 —— O(n²)
步骤:
排序:将数组升序排列。
固定一个数
a(即nums[i])。在该数后面的区间内,利用“双指针算法”快速找到两个数的和等于
-a即可。
手写图示:
排序后数组:[-4, -4, -1, 0, 0, 0, 1, 1, 4, 4, 5, 6]
→ 固定i=0(值为-4),left=1,right=11,目标值t=4。
→ 指针移动过程图示清晰。
处理细节问题:
1. 去重:
找到一种结果之后,
left和right指针要跳过重复元素:while(left < right && nums[left] == nums[left - 1]) left++; while(left < right && nums[right] == nums[right + 1]) right--;当使用完一次双指针算法之后,
i也需要跳过重复元素:while(i < n && nums[i] == nums[i - 1]) i++;
2. 不漏:
找到一种结果之后,不要“停”,缩小区间,继续寻找。
优化点:
固定数
a <= 0:因为数组已排序,若a > 0,则后续所有数都大于0,三数之和不可能为0,可直接break。避免越界:指针移动时需保证
left < right。
3. 编写代码
class Solution { // 时间复杂度 O(N^2) public List<List<Integer>> threeSum(int[] nums) { List<List<Integer>> ret = new ArrayList<>(); // 1. 排序 Arrays.sort(nums); // 2. 利用双指针解决问题 int n = nums.length; for(int i = 0; i < n; ) { if(nums[i] > 0) break; // 小优化 int left = i + 1, right = n - 1, target = -nums[i]; while(left < right) { int sum = nums[left] + nums[right]; if(sum > target) right--; else if(sum < target) left++; else { // nums[i] nums[left] nums[right] ret.add(new ArrayList<Integer>(Arrays.asList(nums[i], nums[left], nums[right]))); left++; right--; // 缩小区间继续寻找 // 去重: left right while(left < right && nums[left] == nums[left - 1]) left++; while(left < right && nums[right] == nums[right + 1]) right--; } } // 去重: i i++; while(i < n && nums[i] == nums[i - 1]) i++; } return ret; } }总结
核心思想:排序 + 双指针,将 O(n³) 优化至 O(n²)。
关键点:
排序是基础。
固定一个数,双指针找另外两个数。
去重处理:三个地方(
i,left,right)都要跳过重复元素。小优化:
nums[i] > 0时直接 break,避免无效计算。
易错点:去重逻辑必须严格,否则会包含重复三元组。
