寻找两个链表交点的核心是"消除两链表长度差",常见方法分为空间复杂度O(1) 和空间复杂度O(n) 两类,以下是具体实现:
节点
代码实现:
ini
class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
next = null;
}
}
一、双指针法(最优,空间O(1),时间O(m+n))
原理
- 利用"两指针遍历总长度相等"消除长度差:设链表A长度为m,链表B长度为n,指针1从A出发、指针2从B出发,遍历完当前链表后立即切换到另一链表头部,最终两指针会走m+n步------若有交点则在交点相遇,无交点则同时为null。
代码实现:
java
//双链表有共同节点
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null) {
return null; // 如果任一链表为空,则没有交点
}
ListNode pointerA = headA;
ListNode pointerB = headB;
// 当两个指针不相等时,继续遍历
while (pointerA != pointerB) {
// 如果指针A到达链表末尾,则重定向到链表B的头部
pointerA = (pointerA == null) ? headB : pointerA.next;
// 如果指针B到达链表末尾,则重定向到链表A的头部
pointerB = (pointerB == null) ? headA : pointerB.next;
}
// 当指针相等时,要么是交点,要么都是null(无交点)
return pointerA;
}
二、长度差法(空间O(1),时间O(m+n))
原理
1.先分别计算两链表的长度lenA和lenB,求出长度差diff = |lenA - lenB|;
2.让"长链表指针"先提前走diff步,使两指针从"距离尾部等长"的位置开始同步遍历;
3.两指针首次相遇的节点即为交点,若遍历结束未相遇则无交点。
代码实现:
ini
//利用链表长度差值
public ListNode getIntersectionNodeLength(ListNode headA, ListNode headB) {
if (headA == null || headB == null) {
return null; // 如果任一链表为空,则没有交点
}
// 计算链表A的长度
int lenA = 0;
ListNode currentA = headA;
while (currentA != null) {
lenA++;
currentA = currentA.next;
}
// 计算链表B的长度
int lenB = 0;
ListNode currentB = headB;
while (currentB != null) {
lenB++;
currentB = currentB.next;
}
// 计算长度差
int diff = Math.abs(lenA - lenB);
// 将较长链表的指针前进diff步
if (lenA > lenB) {
for (int i = 0; i < diff; i++) {
headA = headA.next;
}
} else {
for (int i = 0; i < diff; i++) {
headB = headB.next;
}
}
// 同时遍历两个链表,寻找交点
while (headA != null && headB != null) {
if (headA == headB) {
return headA; // 找到交点
}
headA = headA.next;
headB = headB.next;
}
return null; // 没有交点
}
三、哈希集合法(空间O(n),时间O(m+n))
原理
1.遍历其中一个链表(如A),将所有节点存入哈希集合(利用集合"不可重复"特性);
2.遍历另一个链表(如B),逐个检查节点是否在哈希集合中------首次存在的节点即为交点;
3.若遍历结束未找到,则无交点。
代码实现:
csharp
//哈希表法
public ListNode getIntersectionNodeHash(ListNode headA, ListNode headB) {
HashSet<ListNode> nodesInB = new HashSet<ListNode>();
// 将链表B的所有节点存入哈希表
while (headB != null) {
nodesInB.add(headB);
headB = headB.next;
}
// 遍历链表A,检查是否有节点在哈希表中
while (headA != null) {
if (nodesInB.contains(headA)) {
return headA; // 找到交点
}
headA = headA.next;
}
return null; // 没有交点
}
四、 测试
代码实现:
ini
//测试
public static void main(String[] args) {
linkedHasNode solution = new linkedHasNode();
// 创建两个链表
ListNode common = solution.new ListNode(8);
common.next = solution.new ListNode(4);
common.next.next = solution.new ListNode(5);
ListNode headA = solution.new ListNode(4);
headA.next = solution.new ListNode(1);
headA.next.next = common; // 链表A连接到公共节点
ListNode headB = solution.new ListNode(5);
headB.next = solution.new ListNode(0);
headB.next.next = solution.new ListNode(1);
headB.next.next.next = common; // 链表B连接到公共节点
// 查找交点
//双指针法
// ListNode intersection = solution.getIntersectionNode(headA, headB);
//哈希法
// ListNode intersection = solution.getIntersectionNodeHash(headA, headB);
//差值法
ListNode intersection = solution.getIntersectionNodeLength(headA, headB);
if (intersection != null) {
System.out.println("Intersection at node with value: " + intersection.val);
} else {
System.out.println("No intersection.");
}
}
测试结果:
sql
Intersection at node with value: 8
五、方法对比与选择
方法 | 时间复杂度 | 空间复杂度 | 核心优势 | 适用场景 |
---|---|---|---|---|
双指针法 | O(m+n) | O(1) | 最优空间,无额外存储 | 内存敏感场景 |
长度差法 | O(m+n) | O(1) | 逻辑直观,易理解 | 新手学习、代码可读性优先 |
哈希集合法 | O(m+n) | O(n) | 实现简单,无需处理长度差 | 空间充足、追求代码简洁 |
实际工程中优先选择双指针法,兼顾时间与空间效率。