题目描述:
单链表相交指的是两个链表存在完全重合的部分,如下图所示:
在上图中,这两个链表相交于结点5,要求判断两个链表是否相交,如果相交,找出相交处的结点。
方法:
-
Hash 法
如上图所示,如果两个链表相交,那么它们一定会有公共的结点,由于结点的地址可以作为结点的唯一标识,所以可以通过判断两个链表中的结点是否有相同的地址或引用来判断链表是否相交。具体可以采用如下方法来实现:
首先遍历链表 head1,把遍历到的所有结点的地址存放到 HashSet 中;接着遍历链表 head2,每遍历到一个结点,就判断这个结点的地址在 HashSet 中是否存在,如果存在,那么说明两个链表相交并且当前遍历到的结点就是它们的相交点,否则直接将链表 head2 遍历结束,说明这两个单链表不相交。算法性能分析:
由于这种方法需要分别遍历两个链表,因此算法的时间复杂度为O(n1 + n2),其中 n1 ,n2 分别为两个链表的长度。
此外,由于需要申请额外的存储空间来存储链表 head1 中结点的地址,因此算法的空间复杂度为 O(n1)。 -
首尾相接法
将这两个链表首尾相连(例如将链表 head1 尾结点链接到 head2 的头指针),然后检测这个链表是否存在环,如果存在,则两个链表相交,而环的入口即为相交的结点。如下图所示:
所以后续的工作即是判断连接后的链表是否存在环。算法性能分析:
判断一个链表是否存在环,参考题1.6 -
尾结点法
如果两个链表相交,那么两个链表从相交点到链表结束都是相同的结点,必然是Y字形(如上图),所以判断两个链表的最后一个结点是不是相同即可。即先遍历一个链表,直到尾部,再遍历另外一个链表,如果也可以走到同样的结尾点,则两个链表相交,这时记下两个链表的长度n1、n2,再遍历一次,长链表结点先出发 |n1 - n2| 步 (因为两个链表相交之后的子链表长度相同,长度相差的地方在交点之前),之后两个链表同时前进,每次一步,相遇的第一点即为两个链表相交的第一个点。算法性能分析:
假设这两个链表长度分别为 n1,n2,重叠的结点个数为 L (0<L<min(n1,n2)),则总共对链表进行遍历的次数为 n1+n2+L+n1-L+n2-L=2(n1+n2)-L,所以算法的时间复杂度为O(n1+n2);
由于这种方法只使用了常数个额外指针变量,所以空间复杂度为O(1)。代码实现:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# @Time : 2020/1/18 12:40
# @Author : buu
# @Software: PyCharm
# @Blog :https://blog.csdn.net/weixin_44321080
class LNode:
def __init__(self, data=None, next=None):
self.data = data
self.next = next
def IsIntersected(head1, head2):
"""
判断两个链表是否相交,如果相交,则找出交点
:param head1: 第一个链表的头结点
:param head2: 第二个链表的头结点
:return: 如果不相交则返回None,否则返回相交结点
"""
if head1 == None or head1.next == None or head2 == None or head2.next == None or \
head2 == head1:
return None
tmp1 = head1.next
tmp2 = head2.next
n1, n2 = 1, 1
while tmp1.next != None:
tmp1 = tmp1.next
n1 += 1 # 遍历第一个链表,记录其长度
while tmp2.next != None:
tmp2 = tmp2.next
n2 += 1 # 遍历第二个链表,记录其长度
if tmp1 == tmp2:
if n1 > n2:
while n1 - n2 > 0: # 长链表先走 n1-n2 步
head1 = head1.next
n1 -= 1
if n2 > n1:
while n2 - n1 > 0:
head2 = head2.next
n2 -= 1
while head1 != head2:
head1 = head1.next
head2 = head2.next
return head1
else:
return None
def lengthOflist(head):
"""
计算列表长度
:param head: 头结点
:return: 返回列表长度
"""
if head is None or head.next is None:
return 0
else:
tmp = head.next
cnt = 1
while tmp.next is not None:
tmp = tmp.next
cnt += 1
return cnt
def printList(head):
"""
把链表打印出来
:param head:
:return:
"""
if head is None or head.next is None:
return
cur = head.next
while cur is not None:
print(cur.data, end=' ')
cur = cur.next
if __name__ == '__main__':
i = 1
head1, head2 = LNode(), LNode()
tmp = None
cur = head1
p = None
while i < 8: # 构造第一个链表
tmp = LNode(i)
cur.next = tmp
cur = tmp
if i == 5:
p = tmp
i += 1
cur = head2
i = 1
while i < 5:
tmp = LNode(i)
cur.next = tmp
cur = tmp
i += 1
cur.next = p # 使它们相交于结点5
interNode = IsIntersected(head1, head2)
if interNode == None:
print('no intersection!')
else:
# cnt1=lengthOflist(head1)
# cnt2=lengthOflist(head2)
print('l1: ', end=' ')
printList(head1)
print('\nl2: ', end=' ')
printList(head2)
print('\nintersection:', str(interNode.data))
结果:
引申:
如果单链表有环,如何判断两个链表是否相交
思路:
(1) 如果一个单链表有环,另外一个没有环,则两个链表肯定不相交;
(2) 如果两个单链表都有环且相交,那么这两个链表一定共享这个环。
判断两个有环的单链表是否相交的方法为:
首先采用1.6题的方法找到链表 head1 中环的入口点 p1,然后遍历链表 head2,判断链表中是否包含结点 p1,如果包含,则这两个链表相交,否则不相交。
找相交点的方法为:
把结点 p1 看作两个两个链表的尾结点,这样就可以把问题转换为求两个无环链表相交点的问题。
end