【题目】给定两个可能有环也可能无环的单链表,头节点head1和head2。请实现一个函数,如果两个链表相交,请返回相交的第一节点,否则返回null
【要求】如果两个链表长度之和为N,时间复杂度请达到0(N),额外空间复杂度请达到0(1)。
1 分析:
解决此问题,需要有两个步骤,第一步是判断链表是否有环,第二个是判断两个链表是否相交;由此会产生三种情况,
2 判断链表是否有环的两个方法(解决步骤一):
2.1 方法一:
思想:
利用Hash表,每遍历到一个结点时,先判断该结点是否已经存在于Hash表中,如果存在就说明该链表有环,否则就将该结点放入到HashSet数据结构中去,就这样直到遍历到空结点。
优缺点:
因为会使用额外的数据结构,容易实现,但是空间复杂度较大, 推荐在笔试中使用,能加快答题速度但是面试不推荐
代码:
// 本题代码中自写的单链表数据结构
static class Node {
int value;
Node next;
Node(int val) {
value = val ;
}
}
public static ListNode getLoopNode(ListNode head)
{
HashSet nodeSet = new HashSet<>();
ListNode cur = head;
while (cur != null)
{
if (!nodeSet.contains(cur))
{
nodeSet.add(cur);
}
else return cur;
cur = cur.next;
}
return null;
}
2.2 方法二
思想:
(1)利用快慢指针解决,快指针跳两步,慢指针每次跳两步,如果有环,那么快指针一定会在环内和慢指针相遇,
(2)而且慢指针最多会在环内走两圈,一定会和快指针相遇;
(3)如果相遇后,快指针回到链首,慢指针位置不变,两者每次都走一步,则她们一定会在环的入口处相遇
(4)如果快慢指针刚好在环的入口处相遇,那么环内和环外结点的数量刚好相等
证明:设环外m,环内n节点,相遇时慢指针在环内走k步。此时慢走m+k步,快比慢多走m+k(快在绕圈圈等着慢),结论(m+k)%n=0。快回起点走m步到环入口,此时慢在环内走了(k+m步)%n=0,即在环入口相遇
优缺点:
空间复杂度低,实现稍微复杂一些,适合面试的时候向面试官展示你的风采
代码
public static Node getLoopNode2(Node head)
{
if (head == null || head.next == null || head.next.next == null )
return null;
Node fast = head.next.next;
Node slow = head.next;
while (fast != slow )
{
if (fast.next.next == null || fast.next == null)
return null;
slow = head.next;
fast = head.next.next;
}
fast = head;
while (fast != slow)
{
fast = fast.next;
slow = slow.next;
}
return fast;
}
3 求两链表交点的算法实现
3.1 情况一实现:
当两个链表都是无环链表时:
public static Node findIntersectionWithoutLoop(Node head1, Node head2)
{
if (head1 == null || head2 == null) return null;
int n = 0;
Node cur1 = head1;
Node cur2 = head2;
while (cur1 != null)
{
n++;
cur1 = cur1.next;
}
// 下面这个while循环后,n的绝对值变为较长链表的长度减去较短链表的值
while (cur2 != null)
{
n--;
cur2 = cur2.next;
}
// 遍历完两个链表后,如果他们的链尾结点不是同一个结点,则直接返回空
if (cur1 != cur2) return null;
// 以下两行代码总能使得较长的链表赋值给cur1,较短的赋值给cur2
cur1 = n > 0 ? head1 : head2; // 谁长,谁就变成cur1
cur2 = n > 0 ? 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;
}
3.2 情况二不存在,略
3.3 情况三的实现
如果两个链表都是有环链表:
分析:如果两个链表都有环,要么不相交,要么相交共用环,对于共用环,也有两种情况:
代码
// 这里的loop1和loop2分别是指链表head1和head2的入环结点
public static Node findIntersectionWithBothLoop(Node head1, Node loop1, Node head2, Node loop2)
{
Node cur1 = null;
Node cur2 = null;
if (loop1 == loop2)
{
int n = 0;
while (cur1 != loop1)
{
n++;
cur1 = cur1.next;
}
// 下面这个while循环后,n的绝对值变为较长链表的长度减去较短链表的值
while (cur2 != loop2)
{
n--;
cur2 = cur2.next;
}
// 遍历完两个链表后,如果他们的链尾结点不是同一个结点,则直接返回空
if (cur1 != cur2) return null;
// 以下两行代码总能使得较长的链表赋值给cur1,较短的赋值给cur2
cur1 = n > 0 ? head1 : head2; // 谁长,谁就变成cur1
cur2 = n > 0 ? 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;
}
else
{
cur1 = loop1.next;
while (cur1 != loop1)
{
if (cur1 == loop2) return loop1; // 返回loop1和loop2都可以,都称为两个链表的第一个相交的结点
cur1 = cur1.next;
}
return null; // 如果cur1的指针转回到环的入口时,则此时两个链表不相交
}
}
3.4 三种情况合并到一起
// 综合三种情况
public static Node getIntersectNode(Node head1, Node head2)
{
if (head1 == null || head2 == null) return null;
// 使用getLoopNode判断属于哪一种情况
Node loop1 = getLoopNode(head1);
Node loop2 = getLoopNode(head2);
// 两个链表都是无环链表
if (loop1 == null && loop2 == null)
return findIntersectionWithoutLoop(head1, head2);
// 如果两个链表都是有环链表
if (loop1 != null && loop2 != null)
return findIntersectionWithBothLoop(head1, loop1, head2, loop2);
return null;
}
3.5 完整代码
package ListNode;
import java.util.HashSet;
public class IsIntersection {
public static void main(String[] args) {
}
static class Node {
int value;
Node next;
Node(int val) {
value = val ;
}
}
public static Node getLoopNode(Node head)
{
if (head == null || head.next == null || head.next.next == null )
return null;
HashSet nodeSet = new HashSet<>();
Node cur = head;
while (cur != null)
{
if (!nodeSet.contains(cur))
{
nodeSet.add(cur);
}
else return cur;
cur = cur.next;
}
return null;
}
public static Node getLoopNode2(Node head)
{
if (head == null || head.next == null || head.next.next == null )
return null;
Node fast = head.next.next;
Node slow = head.next;
while (fast != slow )
{
if (fast.next.next == null || fast.next == null)
return null;
slow = head.next;
fast = head.next.next;
}
fast = head;
while (fast != slow)
{
fast = fast.next;
slow = slow.next;
}
return fast;
}
public static 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 findIntersectionWithoutLoop(head1, head2);
// 如果两个链表都是有环链表
if (loop1 != null && loop2 != null)
return findIntersectionWithBothLoop(head1, loop1, head2, loop2);
return null;
}
public static Node findIntersectionWithBothLoop(Node head1, Node loop1, Node head2, Node loop2)
{
Node cur1 = null;
Node cur2 = null;
if (loop1 == loop2)
{
int n = 0;
while (cur1 != loop1)
{
n++;
cur1 = cur1.next;
}
// 下面这个while循环后,n的绝对值变为较长链表的长度减去较短链表的值
while (cur2 != loop2)
{
n--;
cur2 = cur2.next;
}
// 遍历完两个链表后,如果他们的链尾结点不是同一个结点,则直接返回空
if (cur1 != cur2) return null;
// 以下两行代码总能使得较长的链表赋值给cur1,较短的赋值给cur2
cur1 = n > 0 ? head1 : head2; // 谁长,谁就变成cur1
cur2 = n > 0 ? 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;
}
else
{
cur1 = loop1.next;
while (cur1 != loop1)
{
if (cur1 == loop2) return loop1; // 返回loop1和loop2都可以,都称为两个链表的第一个相交的结点
cur1 = cur1.next;
}
return null; // 如果cur1的指针转回到环的入口时,则此时两个链表不相交
}
}
public static Node findIntersectionWithoutLoop(Node head1, Node head2)
{
if (head1 == null || head2 == null) return null;
int n = 0;
Node cur1 = head1;
Node cur2 = head2;
while (cur1 != null)
{
n++;
cur1 = cur1.next;
}
// 下面这个while循环后,n的绝对值变为较长链表的长度减去较短链表的值
while (cur2 != null)
{
n--;
cur2 = cur2.next;
}
// 遍历完两个链表后,如果他们的链尾结点不是同一个结点,则直接返回空
if (cur1 != cur2) return null;
// 以下两行代码总能使得较长的链表赋值给cur1,较短的赋值给cur2
cur1 = n > 0 ? head1 : head2; // 谁长,谁就变成cur1
cur2 = n > 0 ? 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;
}
}