1. 从物理观察到分析推导
-
题目分析:
观察物理结构: 如果两条单向链表相交,它们必然呈现 “Y型结构”。基于物理结构的推导,单向通行且路径共享。一旦两个节点在某一点汇合,剩下的路程(直到尾部)对于两者来说是完全一样的。
这意味着:相交点之后的长度相等,且尾节点相同。
-
当前阻碍:
为什么简单地“一起走”不行?
因为两条链表的 “起跑线”不同(头部长度不一致)。
如果 A 链长 10,B 链长 8,两个指针同时出发,当 B 到达交点时,A 还在两步之外。这种相位差(Phase Shift)导致我们无法在交点相遇。
2. 踩坑记录
在做题过程中,我们通过画出数据结构的图可以很容易地得到一个清晰的结论,两条存在相交点的单链表,会形成一个Y型结构,且从相交点开始一直到尾巴节点,两条链表是重合的,是相同的。 所以我们可以从一个相对笨拙的方法入手,先遍历对比结尾点是否相同,可以得出是否有相交点,同时还可以记录每一条链表的长度,方便我们后续找节点的时候对齐两条链表开头的长度。
3. 适配器策略
这里我们用最朴素的工程测量法解决问题。
-
核心策略:测量 -> 对齐 -> 同步。
-
积木调用:
- 首先调用 [线性遍历] 的变体(全量度量),获取两条链表的物理属性(长度 & 尾节点)。
- 根据长度差,调用 [差值对齐] ,消除起跑线差异。
- 最后再次使用 [线性遍历] 进行同步比对。
4. 代码组装
/**
* Definition for singly-linked list.
* class ListNode {
* val: number
* next: ListNode | null
* constructor(val?: number, next?: ListNode | null) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
* }
*/
// --- 积木定义区 ---
// 积木 A: [线性遍历] 变体 -> 获取长度和尾节点
function getListMetrics(head: ListNode | null): { length: number; tail: ListNode | null } {
let len = 0;
let curr = head;
let tail = null;
// 积木核心:标准的线性遍历骨架
while (curr !== null) {
len++;
tail = curr; // 记录当前轨迹
curr = curr.next;
}
return { length: len, tail: tail };
}
// 积木 B: [差值对齐] -> 让指针先行 delta 步
function advancePointer(node: ListNode | null, delta: number): ListNode | null {
let curr = node;
while (delta > 0 && curr !== null) {
curr = curr.next;
delta--;
}
return curr;
}
// --- 主逻辑区 ---
function getIntersectionNode(headA: ListNode | null, headB: ListNode | null): ListNode | null {
if (!headA || !headB) return null;
// Step 1: 物理勘测 (利用积木 A)
const metricsA = getListMetrics(headA);
const metricsB = getListMetrics(headB);
// 核心剪枝:如果尾巴不同,说明是平行线,物理上不可能相交
if (metricsA.tail !== metricsB.tail) {
return null;
}
// Step 2: 计算相位差
let ptrA = headA;
let ptrB = headB;
const delta = Math.abs(metricsA.length - metricsB.length);
// Step 3: 相位对齐 (利用积木 B)
// 谁长谁先跑,跑完之后大家距离终点就一样远了
if (metricsA.length > metricsB.length) {
ptrA = advancePointer(ptrA, delta);
} else {
ptrB = advancePointer(ptrB, delta);
}
// Step 4: 同步查找
// 此时回到最基础的 [线性遍历] 模式,同步步进
while (ptrA !== ptrB) {
ptrA = ptrA!.next;
ptrB = ptrB!.next;
}
return ptrA; // 相遇点即为交点
}
5. 相关算法积木
积木 A:全量度量器 (The Metric Scanner)
可以看做是 🧊 算法积木:单链表的「线性遍历」 的一种变体。标准遍历只走过程,而这个变体目的是采集数据。
- 输入:链表头。
- 输出:{ length, tail }。
- 作用:根据“同尾必相交”原理进行快速剪枝,并提供长度数据。
积木 B:差值对齐器 (Gap Aligner)
直接调用 🧊 算法积木:单链表中的「差值对齐」 。
- 输入:较长的链表头, 步数 delta。
- 输出:对齐后的新起点。
- 作用:将“追击问题”转化为“相遇问题”。