本文目录
问题:剑指 Offer 52. 两个链表的第一个公共节点
输入两个链表,找出它们的第一个公共节点。
如下面的两个链表:
在节点 c1 开始相交。
解答一:暴力枚举
解答思路
依次遍历链表 A 和链表 B 中的每个节点,直到找到相同的节点。
Java 代码实现
/**
* 暴力枚举
*
* @param headA 链表 A 的首节点
* @param headB 链表 B 的首节点
* @return 两个链表的第一个公共节点
*/
public static ListNode getIntersectionNode1(ListNode headA, ListNode headB) {
if (headA == null || headB == null) {
return null;
}
for (ListNode nodeA = headA; nodeA != null; nodeA = nodeA.next) {
for (ListNode nodeB = headB; nodeB != null; nodeB = nodeB.next) {
if (nodeA == nodeB) {
return nodeA;
}
}
}
return null;
}
时间和空间复杂度
时间复杂度:
O
(
m
∗
n
)
O(m*n)
O(m∗n)
空间复杂度:
O
(
1
)
O(1)
O(1)
解答二:使用集合
解答思路
将链表 A 中的所有节点放入到一个集合 A 中,依次遍历链表 B 中的每个节点,并判断当前节点是否在集合 A 中。
Java 代码实现
/**
* 使用集合
*
* @param headA 链表 A 的首节点
* @param headB 链表 B 的首节点
* @return 两个链表的第一个公共节点
*/
public static ListNode getIntersectionNode2(ListNode headA, ListNode headB) {
if (headA == null || headB == null) {
return null;
}
HashSet<ListNode> nodeHashSetA = new HashSet<>();
ListNode nodeA = headA;
while (nodeA != null) {
nodeHashSetA.add(nodeA);
nodeA = nodeA.next;
}
ListNode nodeB = headB;
while (nodeB != null) {
if (nodeHashSetA.contains(nodeB)) {
return nodeB;
}
nodeB = nodeB.next;
}
return null;
}
时间和空间复杂度
时间复杂度:
O
(
m
+
n
)
O(m+n)
O(m+n)
空间复杂度:
O
(
m
)
O(m)
O(m)
解答三:使用栈
解答思路
先将链表 A 中的所有元素放入栈 A 中,链表 B 中的所有元素放入栈 B 中,然后让栈 A 和 栈 B 中的元素依次出栈,最后一个出栈的共同元素即是第一个公共子节点。该方法相当于从后往前找两个链表的第一个公共子节点。
该方法的执行流程示例如下图所示:
Java 代码实现
/**
* 使用栈
*
* @param headA 链表 A 的首节点
* @param headB 链表 B 的首节点
* @return 两个链表的第一个公共节点
*/
public static ListNode getIntersectionNode3(ListNode headA, ListNode headB) {
if (headA == null || headB == null) {
return null;
}
Stack<ListNode> stackA = new Stack<>();
Stack<ListNode> stackB = new Stack<>();
ListNode nodeA = headA;
while (nodeA != null) {
stackA.push(nodeA);
nodeA = nodeA.next;
}
ListNode nodeB = headB;
while (nodeB != null) {
stackB.push(nodeB);
nodeB = nodeB.next;
}
ListNode intersectionNode = null;
while (stackA.size() > 0 && stackB.size() > 0) {
if (stackA.peek() == stackB.peek()) {
intersectionNode = stackA.pop();
stackB.pop();
} else {
break;
}
}
return intersectionNode;
}
时间和空间复杂度
时间复杂度:
O
(
m
+
n
)
O(m+n)
O(m+n)
空间复杂度:
O
(
m
+
n
)
O(m+n)
O(m+n)
解答四:双指针
解答思路
可以将链表分为左右两个部分,则右边部分为两个链表的公共节点,如下图所示:
可以发现如果链表 A 后面链结链表 B,链表 B 后面链结链表 A,则最后一部分链表 A 和链表 B 中的节点是一样的,一样部分的第一个节点便是要找的两个链表的第一个公共子节点,也就是说分别遍历链表A-B 和链表 B-A 则遍历到某个位置时就能找到相交的点。
按照这个思路我们其实可以在每个链表访问完以后,再调整到链表的表头继续遍历,这样可以不用新建链表。
该方法的执行流程示例如下图所示:
Java 代码实现
/**
* 双指针
*
* @param headA 链表 A 的首节点
* @param headB 链表 B 的首节点
* @return 两个链表的第一个公共节点
*/
public static ListNode getIntersectionNode4(ListNode headA, ListNode headB) {
if (headA == null || headB == null) {
return null;
}
ListNode p1 = headA;
ListNode p2 = headB;
while (p1 != p2) {
p1 = p1.next;
p2 = p2.next;
if (p1 != p2) {
if (p1 == null) {
p1 = headB;
}
if (p2 == null) {
p2 = headA;
}
}
}
return p1;
}
注意:如果 p1 = p1.next; p2 = p2.next;后没有先判断 p1 != p2 再去判断p1 == null 和 p2 == null 的话可能会导致死循环。
该示例如下图所示:
从图中可以看到在这种情况下,会出现 p1 == p2 == null 然后 p1 和 p2 都改变了指向重新回头反复遍历的问题。
时间和空间复杂度
时间复杂度:
O
(
m
+
n
)
O(m+n)
O(m+n)
空间复杂度:
O
(
1
)
O(1)
O(1)
解答五:差和双指针
解答思路
首先求出链表 A 的长度 lengthA 和链表 B 的长度 lengthB,然后让长度长的链表先走
∣
l
e
n
g
t
h
A
−
l
e
n
g
t
h
B
∣
\left | lengthA-lengthB \right |
∣lengthA−lengthB∣步,最后再同时遍历两个链表,找到的第一个相同结点便是两个链表的第一个公共子节点。
该方法的执行流程示例如下图所示:
Java 代码实现
/**
* 差和双指针
*
* @param headA 链表 A 的首节点
* @param headB 链表 B 的首节点
* @return 两个链表的第一个公共节点
*/
public static ListNode getIntersectionNode5(ListNode headA, ListNode headB) {
if (headA == null || headB == null) {
return null;
}
// 计算链表 A 的长度
ListNode nodeA = headA;
int lengthA = 0;
while (nodeA != null) {
lengthA++;
nodeA = nodeA.next;
}
// 计算链表 B 的长度
ListNode nodeB = headB;
int lengthB = 0;
while (nodeB != null) {
lengthB++;
nodeB = nodeB.next;
}
// 计算链表 A 和链表 B 的长度差
int sub = lengthA > lengthB ? lengthA - lengthB : lengthB - lengthA;
ListNode p1 = headA;
ListNode p2 = headB;
// 长的先走 sub 步
if (lengthA > lengthB) {
int count = 0;
while (count < sub) {
p1 = p1.next;
count++;
}
}
if (lengthB > lengthA) {
int count = 0;
while (count < sub) {
p2 = p2.next;
count++;
}
}
// 同时遍历两个链表
while (p1 != p2) {
p1 = p1.next;
p2 = p2.next;
}
return p1;
}
时间和空间复杂度
时间复杂度:
O
(
m
+
n
)
O(m+n)
O(m+n)
空间复杂度:
O
(
1
)
O(1)
O(1)
总结
针对两个链表第一个公共子节点问题,本文给出了 5 种解答思路和相应的代码实现。其实无论是哪一种思路,都涉及到了遍历链表中的元素,而不同的方法选用了不同的数据结构。
获取本文全部示例代码文件
本文全部示例代码文件可从我的 GitHub 仓库中获取:算法通关村第一关——链表经典问题之两个链表第一个公共子节点笔记代码