前言
本文介绍链表相关内容,判断两个可能存在环形结构的链表是否相交。
正文
给出两个可能有环也可能无环的单链表,头结点分别为head1和head2,写出一个函数,如果两个链表相交,请返回相交的第一个节点,如果不相交则返回null。
函数实现要求:如果两个链表长度之和为N,时间复杂度要达到O(N),额外空间复杂度要达到O(1)。
思路分析
先看一下有环链表和无环链表的图示:
因为给出的链表并不确定是否包含环形结构,所以,在实现的时候,要先判断链表是否有环。
先实现一个判断是否有环的函数,实现点如下:
- 如果链表中不含有环形结构,函数返回
null。 - 如果链表中含有环形结构,函数返回环形的第一个节点(如图中的
d节点)。
判断是否有环
判断是否有环有多种方式,其中一个方式就是使用HashSet结构,循环一个单链表,将一个个节点放入HashSet中,如果循环最后某一个节点的下一个为null,说明这个链表为无环结构。
在放入HashSet前,先判断HashSet中是否含有即将要放入的节点,如果存在的话,则说明这个链表为有环链表。
快慢指针判断
还可以使用快慢指针来判断链表是否有环,判断方式如下:
- 定义两个指针都指向
head头结点 - 让快指针一次走两个节点,让慢指针一次走一个节点
- 如果快指针遇到
null,说明是无环链表 - 如果快指针和慢指针能够相遇,说明是有环链表
- 相遇后,快指针回到头结点慢指针不动,快慢指针再次一个节点一个节点的走
- 如果快慢指针再次相遇,则,相遇点就是入环的第一个节点。
判断是否相交
判断两个链表是否相交,需要判断几种情况如下:
- 两个链表都无环
- 两个链表其中有一个有环,另一个无环
两个链表都无环
两个无环链表相交示意图:
简单的方式实现就是,准备一个HashSet,把其中一个链表节点都放进去,然后遍历两一个链表判断节点是否在HashSet中即可。
不使用HashSet这样的集合怎么做呢,具体步骤如下:
- 先遍历第一个链表,记录链表长度以及最后一个节点。
- 再遍历另一个链表,记录链表长度以及最后一个节点。
- 如果两个链表的最后一个节点是同一个(内存地址相同),则说明相交了。
那怎样找到第一个相交的节点呢?方式也简单,步骤如下:
- 假设第一个链表长度为
50,第二个链表长度为30。 - 第一个链表先走
20步,然后两个链表依次一步一步走。 - 如果遇到的节点是同一个,则这个节点就是相交的第一个节点。
两个链表其中一个有环
这种情况是不存在的,两个链表不可能相交,直接返回空即可。
两个链表都有环
如果两个链表都有环,也需要分情况来讨论:
- 两个有环链表不相交
- 两个链表相交在环形之前
- 两个链表相交在环形上
针对第二种和第三种情况,图示如下:
可以先判断两个链表的入环节点,如果入环节点是同一个,那么就属于第二种情况,这种情况找第一个相交点跟两个无环链表一致。
针对第一种和第三种情况,判断如下:
- 先找到两个链表的入环节点
- 遍历其中一个链表的环部,看是否能够找到与另一个入环相等的节点
- 如果有,则说明相交
- 如果没有,则不相交
代码实现
先用快慢指针判断下一个链表是否含有环,有环的话返回第一个入环节点,无环的话返回null,代码如下:
public Node getLoopNode(Node head) {
if (head == null || head.next == null || head.next.next == null) {
return null;
}
// 慢指针
Node slow = head.next;
// 快指针
Node fast = head.next.next;
while (slow != fast) {
if (fast.next == null || fast.next.next == null) {
return null;
}
fast = fast.next.next;
slow = slow.next;
}
// 走到这里说明快慢指针能相遇,相遇后,快指针回到头结点
fast = head;
while (slow != fast) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
如果两个链表都没有环,判断是否相交,代码如下:
public Node noLoop(Node head1, Node head2) {
if (head1 == null || head2 == null) {
return null;
}
Node cur1 = head1;
Node cur2 = head2;
int n = 0;
while (cur1.next != null) {
n++;
cur1 = cur1.next;
}
while (cur2.next != null) {
n--;
cur2 = cur2.next;
}
if (cur1 != cur2) {
return null;
}
// n为链表1长度减去链表2长度的值
cur1 = n > 0 ? head1 : head2; // 谁长,谁的头变成cur1
cur2 = cur1 == head1 ? head2 : head1; // 谁短,谁的头变成cur2
n = Math.abs(n);
while (n != 0) {
n--;
cur1 = cur1.next;
}
while (cur1 != cur2) {
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}
两个链表都有环,判断代码如下:
public Node bothLoop(Node head1, Node loop1, Node head2, Node loop2) {
Node cur1 = null;
Node cur2 = null;
if (loop1 == loop2) {
cur1 = head1;
cur2 = head2;
int n = 0;
while (cur1 != loop1) {
n++;
cur1 = cur1.next;
}
while (cur2 != loop2) {
n--;
cur2 = cur2.next;
}
cur1 = n > 0 ? head1 : head2;
cur2 = cur1 == head1 ? head2 : head1;
n = Math.abs(n);
while (n != 0) {
n--;
cur1 = cur1.next;
}
while (cur1 != cur2) {
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
} else {
cur1 = loop1.next;
while (cur1 != loop1) {
if (cur1 == loop2) {
return loop1;
}
cur1 = cur1.next;
}
return null;
}
}
判断逻辑都有了,看下主函数的调用:
public Node getIntersectNode(Node head1, Node head2) {
if (head1 == null || head2 == null) {
return null;
}
Node loop1 = getLoopNode(head1);
Node loop2 = getLoopNode(head2);
if (loop1 == null && loop2 == null) {
return noLoop(head1, head2);
}
if (loop1 != null && loop2 != null) {
return bothLoop(head1, loop1, head2, loop2);
}
return null;
}
总结
本文介绍链表相关内容,判断两个可能存在环形结构的链表是否相交,判断情况有些复杂,需要在考虑的时候仔细分析下,要不然很容易漏掉某一种情况。