题意描述:
编写一个程序,找到两个单链表相交的起始节点。
如下面的两个链表:
在节点 c1 开始相交。
注意:
- 如果两个链表没有交点,返回 null.
- 在返回结果后,两个链表仍须保持原有的结构。
- 可假定整个链表结构中没有循环。
- 程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。
示例:
- 示例一:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Reference of the node with value = 8
输入解释:相交节点的值为 8 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
- 示例二:
输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Reference of the node with value = 2
输入解释:相交节点的值为 2 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。
- 示例三:
输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
输入解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
解释:这两个链表不相交,因此返回 null。
解题思路:
Alice: 又是链表,还是相交链表,会不会和环形链表有相似的地方。
Bob: 读题呀,就是看两个链表是不是相交,如果不是就返回两个链表相交的首个位置。
Alice: 判断是不是相交好说,如果两个链表是相交的,二者的末尾节点一定是相同的,如果不相交,一定是不同的。
Bob: 怎么寻找相交的起始节点呢?直接弄一个 set 把其中的一个链表中所有节点存起来,然后再遍历另一个链表,如果 在 set 中第一次检测到了某个 节点就是 相加的起始节点。
Alice: 对呀,虽然空间复杂度是O(m+n),但是空间复杂度是O(n)呀。
Bob: emm,我有一个办法,把两个相交链表改成一个环,然后用环形链表里面的方法找到环中的首个节点就是相交链表的首个节点,最后在把环给改回去就好了。
Alice: 啊啊啊,你又这样,看起来完全行得通,但是不太优雅。
Bob: ┓( ´∀` )┏ 没有吧,这样做,最后链表结构并没有变化呀,而且时间复杂度是O(m+n)空间复杂度是O(1)呀,又快又好。
Alice: 一定还有别的方法,更优雅的方法。
-------------------------------------------------------------- Alice & Bob --------------------------------------------------------------
Alice: hhh,我找到了,也是双指针,不过比你构造环形链表找首个节点再复原要简单的多了。
Bob: 我明白了,哈哈哈,果然要优雅多了,而且要比我的更快哦。两个链表同时遍历,如果到了末尾就接着遍历另一个链表,如果是相交链表就会在首个相交节点相遇。哈哈哈,如果不是相交链表也可以通过判断链表末尾是不是相等的来搞定。
Alice: 对呀,😎😎。
Bob: 还有一种解法,分别遍历两遍链表就可以了,第一遍求出两个链表的长度,然后用长度去对齐。比如说 A 链表长度是 8, B 链表长度是 5, 这样我们可以让 A 链表直接向前三步走就 对齐了 剩下的 A链表和 B 链表。
Alice: 然后再用两个指针,分别遍历 剩下的 A 链表和 B 链表,遍历的过程中第一个相同的节点就是首个相交节点。这个解法好呀,简单好理解,时间复杂度还是O(m+n), m,n分别是两个链表中节点的数目,比你先形成环,再找环中的首个节点的方法好多了。
Bob: -_-||
代码:
Python 方法一: 使用 set
存储 A链表中的所有元素,然后查询 B链表中首次出现在 set
中的元素。 空间复杂度 O(m+n)
,时间复杂度 O(1)
。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
nodeAddress = set()
while headA != None:
# 存储 A 列表中的所有节点
nodeAddress.add(headA)
headA = headA.next
while headB != None:
# 查询 B 列表中首次出现在 A 列表中的节点,就是第一个相交节点
if headB in nodeAddress:
return headB
headB = headB.next
return None
Python 方法二: 使用在环形链表一题中用过的快慢双指针法,先将相交链表修改成环形链表,求出节点后,再将其修改回去。时间复杂度O(m+n),空间复杂度O(1)。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
if headA == None or headB == None:
# 注意这里需要判断边界条件
return None
tmpNode = headA
while tmpNode.next != None:
tmpNode = tmpNode.next
tmpNode.next = headB
# 使用A B 两个链表制造一个环形链表
ansNode = self.getFirstNodeInCycle(headA)
# 在环形链表中找到相交的起始节点
tmpNode.next = None
# 恢复两个链表的初始状态
return ansNode;
def getFirstNodeInCycle(self, head) -> ListNode:
# floyd算法求环中的首个节点
cycleNode = self.hasCircle(head)
if cycleNode == None:
return None
else:
left = head
right = cycleNode
while left != right:
left = left.next
right = right.next
return right
def hasCircle(self, head) -> ListNode:
# 双指针法判断一个链表中是否有环,如果有,返回环中的某个节点;否则返回Non。
if head == None:
return None
else:
slow = head
fast = head
# 注意这里快慢双指针的起始位置必须是相同的才能使用floyd算法求解环中的首个节点
while slow != None and fast != None and fast.next != None:
slow = slow.next
fast = fast.next.next
if slow == fast:
return fast
return None
Java 方法二: 环形链表 + 快慢双指针 + floyd算法。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if(headA == null || headB == null){
return null;
}
ListNode tmpNode = headA;
while(tmpNode.next != null){
tmpNode = tmpNode.next;
}
tmpNode.next = headB;
ListNode ans = getFirstNodeInCycle(headA);
tmpNode.next = null;
return ans;
}
public ListNode getFirstNodeInCycle(ListNode head){
ListNode cycleNode = hasCycle(head);
if(cycleNode == null){
return null;
}else{
ListNode left = head;
ListNode right = cycleNode;
while(left != right){
left = left.next;
right = right.next;
}
return right;
}
}
public ListNode hasCycle(ListNode head){
if(head == null){
return null;
}else{
ListNode slow = head;
ListNode fast = head;
while(slow != null && fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
if(slow == fast){
return fast;
}
}
return null;
}
}
}
Python解法三: 双指针
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
if headA == None or headB == None:
return None
nodeA = headA
nodeB = headB
endA = None
endB = None
while True:
if nodeA == nodeB:
return nodeB
if nodeA.next == None:
endA = nodeA
nodeA = headB
if endA != None and endB != None and endA != endB:
#两个链表不相交
return None
else:
nodeA = nodeA.next
if nodeB.next == None:
endB = nodeB
nodeB = headA
if endA != None and endB != None and endA != endB:
#两个链表不相交
return None
else:
nodeB = nodeB.next
Java 解法四: 对齐 + 双指针,(来自《技术之瞳》)
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
int lengthA = 0;
int lengthB = 0;
ListNode nodeA = headA; // 统计两个链表的节点数量
while(nodeA != null){
lengthA ++;
nodeA = nodeA.next;
}
ListNode nodeB = headB;
while(nodeB != null){
lengthB ++;
nodeB = nodeB.next;
}
nodeA = headA;
nodeB = headB; // 对齐两个链表
if(lengthA > lengthB){
for(int i=0; i<lengthA-lengthB; ++i){
nodeA = nodeA.next;
}
}else if(lengthA < lengthB){
for(int i=0; i<lengthB-lengthA; ++i){
nodeB = nodeB.next;
}
}
while(nodeA != null){ // 遍历,开始访问,第一相同的就解
if(nodeA == nodeB){
return nodeB;
}
nodeB = nodeB.next;
nodeA = nodeA.next;
}
return null;
}
}
易错点:
- 测试样例:
8
[4,1,8,4,5]
[5,0,1,8,4,5]
2
3
0
[]
[]
0
0
1
[1,2,3,4,5,6,7,8,9,10,11,12,13]
[1,2,3,4,5,6,7,8,9,10,11,12,13]
0
0
- 答案:
Intersected at '8'
No intersection
Intersected at '1'
总结: