【码道初阶】【LeetCode 160】相交链表:让跑者“起跑线对齐”的智慧

【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. 总结

这道题的解法体现了**"预处理"**的思想。

直接做很难(因为不知道交点在哪里),但如果我们通过预先计算长度,消除了两个链表的差异,问题就退化成了简单的"同步遍历"。

代码简洁、逻辑清晰且没有复杂的边界条件,这就是长度差对齐法的魅力所在。


相关推荐
学长毕设5 小时前
基于SpringBoot的实验室资产管理系统
java·spring boot·后端
zzu123zsw6 小时前
从臃肿的模板调用到声明式契约:深入 UniHttp 动态代理与多渠道生命周期扩展
java·spring boot·microsoft·unihttp
UIU1147 小时前
P1233 [ICPC 2001 Taejon R] 木棍加工
数据结构·c++·算法·动态规划
源码集结号7 小时前
云诊所门诊系统源码,基于SpringBoot+Vue.js架构,支持多租户设计与Docker容器化部署
java·spring boot·源码·医院信息系统·门诊系统·诊所系统
论文复现现场7 小时前
WRF、FEA 能用 L40S 加速吗?求解器检查、断点恢复与长期运行
java·数据库·算法·wrf·有限元分析·gpu计算·断点续算
ZhengEnCi7 小时前
用 AI 写代码为什么拼到最后还是拼人:算力瓶颈与领域专家提示词的本质差异
算法·架构
啃瓜子的松鼠8 小时前
泛微OAe9-后端二开异步调用
java
秋天的怀念_David8 小时前
第二十一夜 · 数字黑洞与西西弗斯的惩罚
java·程序人生·算法
外收内放9 小时前
Python基础语法练习题(73)
java·前端·python
Wang's Blog9 小时前
Java 项目实战: 交友项目 - 环信后台发送消息测试
java·交友