【LeetCode 160】相交链表:让跑者"起跑线对齐"的智慧
在链表问题中,"相交链表"(Intersection of Two Linked Lists)是一道考察指针操作和空间思维的经典题目。
题目要求我们找到两个单链表的交点。难点在于:两个链表的长度可能不一样 。
就像两个从不同岔路口出发的跑者,如果我们要他们在岔路汇合点相遇,必须解决"路程差"的问题。
今天我们就来拆解一种最直观、最稳健的解法:长度差对齐法。
1. 核心思路:消除"贫富差距"
假设链表 A 长 5 米,链表 B 长 3 米,它们在倒数第 2 米处相交。
如果我们同时从头开始遍历,A 还在前半截跑的时候,B 可能已经跑完或者跑过了,两人根本碰不到面。
解法直觉 :
既然 A 比 B 长,那我就算出 A 比 B 长多少(比如长 k),先让 A 提前跑 k 步。此时,A 和 B 距离终点(或交点)的距离就一样了。接下来两人齐头并进,一定会在交点相遇。
2. 代码深度拆解
这个代码完美地贯彻了这个思路,我们将其分为三个阶段:
第一阶段:量尺寸(计算长度)
首先,我们需要知道两条链表各自有多长,从而计算差值。
java
ListNode list1 = headA;
ListNode list2 = headB;
int lenA = 0;
int lenB = 0;
// 遍历 A 获取长度
while(list1 != null) {
list1 = list1.next;
lenA++;
}
// 遍历 B 获取长度
while(list2 != null) {
list2 = list2.next;
lenB++;
}
第二阶段:找差距 & 换跑道(关键逻辑)
这一步是代码的精华。我们需要确定谁长谁短,并计算差值 len。
为了简化后续逻辑,我们通常强制让 list1 指向较长的那个链表。
java
int len = lenA - lenB;
// 【重中之重】刚才遍历完 list1 和 list2 都跑到 null 了,必须拉回起点!
list1 = headA;
list2 = headB;
// 如果 len < 0,说明 B 比 A 长
// 我们交换一下,让 list1 代表长链表,list2 代表短链表
if(len < 0) {
list1 = headB;
list2 = headA;
len = lenB - lenA; // 修正差值为正数
}
代码亮点:
- 状态重置 :
list1 = headA;这一步非常容易忘!很多新手算完长度直接就往下写,结果报空指针,因为此时指针还在链表尾部。 - 动态交换 :通过
if(len < 0)的判断和交换,保证了接下来的代码不需要写两套逻辑,永远只需要操作list1先走。
第三阶段:让长链表先走 & 齐头并进
现在 list1 是长链表,len 是长度差。
java
// 1. 长链表先走 len 步
while(len != 0) {
list1 = list1.next;
len--;
}
// 2. 此时 list1 和 list2 站在了同一起跑线(距离末尾长度相同)
// 一起往后走,直到相遇
while(list1 != list2) {
list1 = list1.next;
list2 = list2.next;
}
// 3. 返回结果
// 如果相交,list1 就是交点;
// 如果不相交,最后 list1 和 list2 都会变成 null,循环结束,返回 null。
return list1;
3. ⚠️ 易错点与避坑指南
结合这段代码,有几个面试或刷题时容易翻车的地方,需要重点标记:
易错点 1:指针复位(Reset State)
在计算完长度后,list1 和 list2 已经指向了链表的末尾(null)。
必须手动将它们重新指向 headA 和 headB 。
你的代码中专门注释了 //记得重置状态!,这是一个非常好的编程习惯。
易错点 2:比较的是节点对象,不是值
题目中强调了:
请注意相交节点的值不为 1... 它们在内存中指向两个不同的位置。
在判断相交时,必须使用 list1 != list2(比较内存地址/引用)。
绝对不能 写成 list1.val != list2.val。因为两条不同的链表可能有数值相同的节点,但它们并不是同一个物理节点(交点)。
易错点 3:如果不相交怎么办?
逻辑上不需要特殊处理。
如果不相交,两个指针会并排走到最后,同时变成 null。
while(list1 != list2) 会因为 null == null 而跳出循环,直接返回 null,符合题目要求。
4. 复杂度分析
- 时间复杂度 :O(N+M)O(N + M)O(N+M)。
- 我们需要遍历 A 和 B 各一次来算长度。
- 然后再遍历一次来找交点。
- 总操作次数约为 2N+2M2N + 2M2N+2M,属于线性时间复杂度。
- 空间复杂度 :O(1)O(1)O(1)。
- 我们只用到了几个指针变量(
list1,list2,len),没有申请额外的数组或哈希表。
- 我们只用到了几个指针变量(
5. 总结
这道题的解法体现了**"预处理"**的思想。
直接做很难(因为不知道交点在哪里),但如果我们通过预先计算长度,消除了两个链表的差异,问题就退化成了简单的"同步遍历"。
代码简洁、逻辑清晰且没有复杂的边界条件,这就是长度差对齐法的魅力所在。