输入两个链表,找出它们的第一个公共结点。
对于公共结点,解释应该是:如果两个单向链表存在公共节点,那么从第一个公共节点开始到最后一个节点都是公共节点。也就是说,相同的点,不仅值相同,后继节点也相同,那么同理公共结点后面的点也是不仅值相同,而且后继节点也相同。这样的话,就可以把两条链条看成 Y 字型了,某一个结点后面的点全部一样。
例如下图中,链表 6 - 7 就是两个链表的公共链表,而节点6就是第一个公共节点。
最直观的当然就是暴力法——双重循环。
那么在第一链表上顺序遍历每个节点,每遍历到一个节点,就在第二个链表上顺序遍历每个节点。如果在第二个链表上有一个节点和第一个链表上的节点一样,则说明两个链表在这个节点上重合。返回第一个相等的节点值即可。这种方法就不做代码展示了。
假设第一个链表长度为m,第二个链表的长度为n。则这种方法的时间复杂度为 O(m∗n)。
我们可以借助快慢指针的思想。先分别遍历链表,计算出两个链表的长度差。然后先移动长链表的指针,使得长短链表的指针距离各自的末尾有相同的距离;再同时移动两个指针,直到出现两指针指向的节点相同为止,那么这个相同节点就是第一个公共节点。
具体来说,使用一个快指针指向长链表,一个慢指针指向短链表。快指针先走两个长度差的步数,然后慢指针再和快指针一起走,指向同一个节点的时候,就是两个链表的第一个公共节点。
这种方法时间复杂度为 O(m + n)。
按照公共节点的概念,我们还可以想到:如果我们能从两个链表的末尾节点开始遍历,找到最后一个相同的节点,那么这个节点就是第一个公共节点。
然而单向链表并不支持反向遍历!在单链表中只能从头结点开始按顺序遍历,最后才能到达尾结点。最后到达的尾结点却要最先被比较,这是 “后进先出” 的特性。
因此我们可以利用栈的性质,维护两个辅助栈,分别把两个链表的节点放入两个栈里,这样两个链表的尾节点就位于两个栈的栈顶。然后依次比较这两个辅助栈的栈顶元素。如果相同,则把栈顶弹出接着比较下一个栈顶,直到找到最后一个相同的结点。
这种方法时间复杂度为 O(m + n),空间复杂度为 O(m + n)。
最少代码也最玄学:用两个指针遍历”两个链表“,最终两个指针到达 None 或者到达公共结点。其实思路和方法二是一样的。
具体分析两个链表的情况如下。
两个指针一起走,步长一致,碰到第一个相同的节点 p1 == p2,退出循环,return p1。
两个指针一起走,直到走到最后一个节点,p1.next 和 p2.next都为 None,满足相等的条件,退出循环,return p1。
两个指针一起走,当一个指针p1走到终点时,说明p1所在的链表比较短,让p1指向另一个链表的头结点开始走,直到p2走到终点;让p2指向短的链表的头结点,那么,接下来两个指针要走的长度就一样了,变成第一种情况。
两个指针一起走,当一个指针p1走到终点时,说明p1所在的链表比较短,让p1指向另一个链表的头结点开始走,直到p2走到终点;让p2指向短的链表的头结点,那么,接下来两个指针要走的长度就一样了,变成第二种情况。
class ListNode:
def __init__(self, data):
self.data = data
self.next = None
def FindLength(head): # 计算链表长度
if head is None:
return 0
length = 0
while head:
length += 1
head = head.next
return length
def FindFirstCommonNode1(head1, head2):
len1 = FindLength(head1)
len2 = FindLength(head2)
lens = abs(len1 - len2) # 两个链表长度的差值
# 哪个链表更长,哪个就先走
if len1 > len2:
while lens:
head1 = head1.next
lens -= 1
elif len1 < len2:
while lens:
head2 = head2.next
lens -= 1
# 再同时在两个链表上遍历
while head1:
if head1 == head2: # 如果两个指针指向了同一个结点,就返回当前结点
return head1.data
head1 = head1.next
head2 = head2.next
def FindFirstCommonNode2(head1, head2):
stack1 = []
stack2 = []
while head1: # 将第一个链表的节点依次放入第一个栈中
stack1.append(head1)
head1 = head1.next
while head2: # 将第二个链表的节点依次放入第二个栈中
stack2.append(head2)
head2 = head2.next
node = ListNode(None)
# 从两个栈顶开始,依次比较,找到最后一个相等的节点
while stack1 and stack2 and stack1[-1] is stack2[-1]:
node = stack1.pop()
stack2.pop()
return node.data # 返回公共节点的值
def FindFirstCommonNode3(head1, head2):
if not head1 or not head2:
return None
p1, p2 = head1, head2
while p1 != p2:
# 如果当前结点为None,就指向另一个链表的头结点开始第二次遍历
p1 = head2 if not p1 else p1.next
p2 = head1 if not p2 else p2.next
return p1.data if p1 else None
def printLinkedList(head):
while head:
print(head.data, end=" ")
head = head.next
print()
if __name__ == '__main__':
# 测试用例1
head1 = ListNode(0)
node1, node2, node3, node4, node5 = ListNode(1), ListNode(4), ListNode(6), ListNode(7), ListNode(8)
head1.next = node1
node1.next = node2
node2.next = node3
node3.next = node4
node4.next = node5
printLinkedList(head1.next)
head2 = ListNode(0)
node6, node7 = ListNode(2), ListNode(3)
head2.next = node6
node6.next = node7
node7.next = node4
printLinkedList(head2.next)
print(FindFirstCommonNode1(head1.next, head2.next))
print(FindFirstCommonNode2(head1.next, head2.next))
print(FindFirstCommonNode3(head1.next, head2.next))
# 测试用例2
head2 = ListNode(0)
node6, node7, node8, node9 = ListNode(2), ListNode(3), ListNode(7), ListNode(8)
head2.next = node6
node6.next = node7
node7.next = node8
node8.next = node9
print(FindFirstCommonNode1(head1.next, head2.next))
print(FindFirstCommonNode2(head1.next, head2.next))
print(FindFirstCommonNode3(head1.next, head2.next))
上面的运行结果为:
1 4 6 7 8
2 3 7 8
7
7
7
None
None
None
到这里,我们的三个方法代码就成功了。
但是如果我把第二个方法的代码写成这样呢:
def FindFirstCommonNode2(head1, head2):
stack1 = []
stack2 = []
while head1: # 将第一个链表的值依次放入第一个栈中
stack1.append(head1.data)
head1 = head1.next
while head2: # 将第二个链表的值依次放入第二个栈中
stack2.append(head2.data)
head2 = head2.next
# 从两个栈顶开始,依次比较,找到最后一个相等的值
node = None
while stack1 and stack2 and stack1[-1] is stack2[-1]:
node = stack1.pop()
stack2.pop()
return node
这时候的运行结果就会变成:
1 4 6 7 8
2 3 7 8
7
7
7
None
7
None
我们可以看到,在第二个测试用例中,方法二的输出结果仍然为7,但另外两种方法返回却是None。为什么会这样?
看一下我使用的两个测试用例,其实值都是相同的。不同在于,第二个测试用例中,第二个链表的节点7、8是创建了新节点,没有使用第一个链表中的节点。
所以此时的方法二中只是把链表中节点的值入栈,依次出栈比较的时候,也只是比较了节点的值是否相同。根本就无法确定它们的后续指针是否指向的同一个地方,那么该方法只找到了值相同的 “公共节点” 而已。
在之前的方法二代码中,我们是把链表上的节点放入栈里;比较栈顶元素是否相等的时候,也是比较的节点是否相等,而不是说仅仅比较节点的值是否相同,这样就能实现了 包含值和后继指针的这整体节点是否是相同的,即真正的公共节点。
而其他方法一直是在链表的基础上从前往后移动比较的,所以后继指针也包含在了比较中,那么就找到了公共结点值和后继指针完全相同的。
也就是说:并不是两个节点的值相同就是公共节点,一定要考虑清楚细节哟。