【码道初阶】【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)

在计算完长度后,list1list2 已经指向了链表的末尾(null)。
必须手动将它们重新指向 headAheadB

你的代码中专门注释了 //记得重置状态!,这是一个非常好的编程习惯。

易错点 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. 总结

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

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

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


相关推荐
菜鸟小芯2 小时前
OpenHarmony环境搭建——02-JDK17安装教程
java
beordie.cloud2 小时前
LeetCode 49. 字母异位词分组 | 从排序到计数的哈希表优化之路
算法·leetcode·散列表
共享家95272 小时前
每日一题(一)
算法
fufu03112 小时前
Linux环境下的C语言编程(四十一)
linux·c语言·算法
原来是好奇心2 小时前
深入Spring Boot源码(二):启动过程深度剖析
java·源码·springboot
听风吟丶2 小时前
Spring Boot 自动配置原理深度解析与实战
java·spring boot·后端
原来是好奇心2 小时前
深入Spring Boot源码(一):环境搭建与初探项目架构
java·gradle·源码·springboot
韩凡2 小时前
JAVA微服务与分布式(概念版)
java·分布式·微服务