永不忘算法:三数之和的“双指针”魔法
大家好!今天我们要解决一道非常经典的算法题——三数之和。这道题之所以经典,是因为它不仅考察逻辑,还考察我们如何“偷懒”(优化效率)。为了让你永远记住它,我们不谈枯燥的定义,直接用生活中的例子来模拟,最后再配合代码实现。
一、 题目是什么?
想象一下,你面前有一堆乱七八糟的数字卡片,比如 [-1, 0, 1, 2, -1, -4]。
你的任务是:从这些卡片中找出所有的“三人组”,要求这三个数字加起来正好等于 0。
注意:
- 不能重复找同一个三人组(比如找到了
(-1, 0, 1),下次就不能再找(-1, 0, 1)了)。 - 三人组里的数字必须是不同位置的卡片(虽然数字可以一样,但位置不能重复)。
二、 为什么暴力解法很“笨”?
最笨的方法是什么?是“三重循环”。
也就是:先拿第一个数,去遍历所有第二个数,再针对每个第二个数,去遍历所有第三个数。这就像是你去超市买东西,把货架上的每一样商品都拿起来看一遍,效率极低。如果数字很多(比如几千个),这种方法算到天黑都算不出来。
我们要学的,是聪明的“双指针”法!
三、 魔法第一步:排排队(排序)
在开始找之前,我们必须先做一件事:把这些数字从小到大排好序。
排序后,上面的例子变成了:[-4, -1, -1, 0, 1, 2]。
为什么要排序?
- 方便去重:数字排在一起,一眼就能看出有没有重复。
- 利用大小关系:这是双指针法的核心!排好序后,左边的数字小,右边的数字大。如果我们想找和为 0 的组合,就可以根据当前的大小来决定是往左找还是往右找。
四、 核心魔法:锁定一个,移动两个
我们可以把问题简化:先确定第一个数,然后去找另外两个数,让这三个数加起来等于 0。
这就好比我们要组一个三人足球队:
- 队长(第一个数):我们从左到右依次选。
- 队员(另外两个数):我们用双指针来找。
图解双指针战术
假设我们选定了队长是 -4(数组第一个数)。此时,我们需要另外两个数加起来等于 4(因为 -4 + 4 = 0)。
我们在队长右边的所有数字里,设置两个“指头”:
- 左指针(L):指向剩余数字里最小的那个(最左边)。
- 右指针(R):指向剩余数字里最大的那个(最右边)。
现在的状态是:
- 队长:
-4 - 剩余数组段:
[-1, -1, 0, 1, 2] - L 指向
-1,R 指向2。
战术规则:
- 计算三数之和:
-4 (队长) + (-1) (L) + 2 (R) = -3。 - 判断:
- 结果是
-3,比目标0小了! - 怎么办? 我们需要让结果变大一点。
- 行动:因为数组是排好序的,L 指针左边的数更小(没用),R 指针右边的数更大(已经没了)。唯一的办法是把 L 指针往右挪,换一个稍微大一点的数。
- 结果是
- 移动 L:L 指向下一个
-1。 - 再次计算:
-4 + (-1) + 2 = -3。还是太小,继续挪 L。 - L 指向 0。计算:
-4 + 0 + 2 = -2。还是小,挪 L。 - L 指向 1。计算:
-4 + 1 + 2 = -1。还是小,挪 L。 - L 和 R 相遇了:说明在这个队长下,找不到合适的队员。换下一个队长!
换个队长试试:
现在队长换成数组第二个数:-1。
剩余数组段:[-1, 0, 1, 2]
- L 指向
-1,R 指向2。
- 计算:
-1 (队长) + (-1) (L) + 2 (R) = 0。 - 判断:中了!正好是 0!
- 记录:把
[-1, -1, 2]放进答案篮子。 - 下一步:既然找到了,L 和 R 都要往中间缩,继续找有没有其他可能。
- 关键去重技巧:在移动 L 之前,先看一眼,下一个数字是不是还是
-1?如果是,直接跳过!因为刚才已经找过-1了,再找就重复了。这就像去食堂打饭,同一个菜不用打两份。
- 关键去重技巧:在移动 L 之前,先看一眼,下一个数字是不是还是
继续移动:
- L 指向
0,R 指向1。
- 计算:
-1 (队长) + 0 + 1 = 0。 - 判断:又中了!
- 记录:把
[-1, 0, 1]放进答案篮子。 - 移动:L 往右,R 往左。L 和 R 碰头了,结束。
五、 动画演示(脑补版)
为了让你更有画面感,请想象以下动画过程:
初始数组:[-4, -1, -1, 0, 1, 2]
【第一轮:队长 -4】
L(-1) ---------------------- R(2)
和太小!L 往右跳 -> 跳 -> 跳 -> 碰到 R,结束。
【第二轮:队长 -1】
L(-1) ------------------ R(2)
和为 0!找到 [-1, -1, 2]!
L 往右跳过重复 -> R 往左
L(0) ------------ R(1)
和为 0!找到 [-1, 0, 1]!
L 往右 -> 碰到 R,结束。
六、 代码实现(看注释就能懂)
把上面的逻辑翻译成 Java 代码,就是这样:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class ThreeSum {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
// 第一步:排排队!从小到大
Arrays.sort(nums);
// 第二步:选队长(遍历数组)
for (int i = 0; i < nums.length - 2; i++) {
// 去重技巧 1:如果这个队长和上一个队长一样,跳过!
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
// 定义双指针
int left = i + 1; // 左指针:队长右边的那个人
int right = nums.length - 1; // 右指针:队尾的那个人
int target = -nums[i]; // 目标:我们需要另外两人加起来等于 -队长
// 第三步:左右夹击
while (left < right) {
int sum = nums[left] + nums[right];
if (sum == target) {
// 找到了!记下来
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
// 去重技巧 2:如果左指针下一个人还一样,直接跳过
while (left < right && nums[left] == nums[left + 1]) {
left++;
}
// 去重技巧 3:如果右指针下一个人还一样,直接跳过
while (left < right && nums[right] == nums[right - 1]) {
right--;
}
// 同时收缩,寻找下一组
left++;
right--;
} else if (sum < target) {
// 和太小了,说明左边的数太小,把左指针往右挪(换大一点的数)
left++;
} else {
// 和太大了,说明右边的数太大,把右指针往左挪(换小一点的数)
right--;
}
}
}
return result;
}
}
七、 总结:怎么保证永不忘?
记住这三个关键词,你就永远不会忘:
- 排序:这是双指针法的前提,没有排序就没有方向感。
- 定一移二:先定住第一个数,剩下的用两个指针从两头往中间凑。
- 去重:选队长时看前一个,移动指针时看下一个,只要数字一样就跳过。
这就是“三数之和”的奥秘。你学会了吗?
参考来源
- Android 图文弹幕 不叠加 不丢失
- Java垃圾回收--强引用、软引用、弱引用、虚引用 详细
- 从文献海洋到学术洞见:一字成文 AI 如何重构文献解读与文献综述范式
- Android图文具体解释属性动画
- 过目不忘单词通:中小学英语单词高效记忆软件免费版实战应用
- Android图文具体解释属性动画
1529



被折叠的 条评论
为什么被折叠?



