有环或无环单链表的相交问题

关于求有环无环单链表相交的问题,是链表类型题目中数一数二的难题,且听一一道来。

一、题目描述

给定两个可能有环也可能无环的单链表,头节点head1和head2。

请实现一个函数,如果两个链表相交,请返回相交的第一个节点。如果不相交,返回null

要求:如果两个链表长度之和为N,时间复杂度请达到O(N),额外空间复杂度请达到O(1)。

二、思路

解决思路分为两大步:

(1)找到单链表的入环节点,无环则返回null

(2)根据得到的链表的入环节点,再展开讨论

1、找到单链表的入环节点,无环则返回null

(1)遍历此单链表,如果某次遍历到的节点为null,则此单链表必定无环

(2)否则有环

准备一个快指针,一个慢指针。开始时,两个指针都从头节点开始,快指针一次走两个节点,慢指针一次走一个节点,直到两个指针相遇。

此时,快指针再从头节点开始走,每次走一个节点;慢指针从相遇处继续往下走,依然一次走一个节点,当两个指针再次相遇时的节点即是入环节点。

2、根据得到的链表的入环节点,再展开讨论

(1)两个链表都无环

遍历其中一个链表,记录链表的长度 length1 和最后一个节点 end1;遍历另外一个链表,记录链表的长度 length2 和最后一个节点 end2。

如果end1和end2不等,则两个链表一定不相交;否则,计算length1和length2的差值 a,长链表先走 a个节点,然后两个链表挨个往下走,第一个相等的节点即是相交的第一个节点。
在这里插入图片描述

(2)一个有环、一个无环

此种情况两个链表是不可能相交的。(大伙可以尝试脑补出这样的结构,记住一点,是单链表

(3)两个链表都有环

分三种情况,各自有环但是不相交;入环节点是同一个;入环节点有两个。

1)各自有环但是不相交

在这里插入图片描述

2)入环节点是同一个

在这里插入图片描述

3)入环节点有两个

在这里插入图片描述

两个链表的入环节点相等:即入环节点是同一个这种情况,求解过程和两个链表都是无环且相交的过程是一样的,也就是求解时完全可以忽略后面的环。

两个链表的入环节点不等:则是第一小种或第三小种。此时,从其中一个入环节点开始,遍历一圈,如果在回到自己时遇到了第二个入环节点,则是第三小种,否则就是第一小种。

三、详细参考代码

1、找到单链表的入环节点

/**
 * 找到单链表的入环节点,无环则返回null
 *
 * @author Java和算法学习:周一
 */
public static Node getLoopNode(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 == null || fast.next.next == null) {
            return null;
        }
        fast = fast.next.next;
        slow = slow.next;
    }

    // 当快慢指针相遇时,快指针再次从头节点开始走
    fast = head;
    while (fast != slow) {
        // 此时就不需要再判断为空了,能走到这里则此单链表必定是有环的
        // 此时快指针一次走一个节点
        fast = fast.next;
        // 慢指针依然一次走一个节点
        slow = slow.next;
    }

    return slow;
}

2、根据得到的链表的入环节点,再展开讨论

(1)两个链表都无环

/**
 * 现在已经知道两个链表无环,找到它们的第一个相交节点
 *
 * @author Java和算法学习:周一
 */
public static Node noLoop(Node head1, Node head2) {
    if (head1 == null || head2 == null) {
        return null;
    }

    Node current1 = head1;
    Node current2 = head2;
    int n = 0;
    // 链表一遍历一遍得到长度、最后一个节点
    while (current1.next != null) {
        n++;
        current1 = current1.next;
    }

    // 链表二遍历一遍得到长度差、最后一个节点
    while (current2.next != null) {
        n--;
        current2 = current2.next;
    }

    // 如果两个链表的最后一个节点不等,则一定不相交
    if (current1 != current2) {
        return null;
    }

    // 长链表的头节点给current1
    current1 = n > 0 ? head1 : head2;
    // 短链表的头节点给current2
    current2 = current1 == head1 ? head2 : head1;

    n = Math.abs(n);
    // 长链表先走n个节点
    while (n != 0) {
        n--;
        current1 = current1.next;
    }

    // 两个链表挨个往下走,第一次相等的节点即是第一个相交的节点
    while (current1 != current2) {
        current1 = current1.next;
        current2 = current2.next;
    }

    return current1;
}

(3)两个链表都有环

/**
 * 两个链表都有环
 * 分三种情况,各自有环但是不相交;入环节点是同一个;入环节点有两个     
 *
 * @author Java和算法学习:周一
 */
public static Node bothLoop(Node head1, Node loop1, Node head2, Node loop2) {
    Node current1 = null;
    Node current2 = null;
    if (loop1 == loop2) {
        current1 = head1;
        current2 = head2;
        int n = 0;
        // 计算head1链表有多少个节点
        while (current1 != loop1) {
            n++;
            current1 = current1.next;
        }

        // 计算两个链表节点数的差值
        while (current2 != loop2) {
            n--;
            current2 = current2.next;
        }

        // 长链表的头节点给current1
        current1 = n > 0 ? head1 : head2;
        // 短链表的头节点给current2
        current2 = current1 == head1 ? head2 : head1;

        n = Math.abs(n);
        // 长链表先走n个节点
        while (n != 0) {
            current1 = current1.next;
            n--;
        }

        // 两个链表挨个往下走,第一次相等的节点即是第一个相交的节点
        while (current1 != current2) {
            current1 = current1.next;
            current2 = current2.next;
        }

        return current1;
    } else {
        // 两个链表的入环节点不等,则是各自有环但是不相交或者入环节点有两个
        current1 = loop1.next;
        // 从其中一个入环节点开始,遍历一圈,如果在回到自己时遇到了第二个入环节点,则是 入环节点有两个
        while (current1 != loop1) {
            if (current1 == loop2) {
                // 此时两个入环节点都是第一个相交节点,返回其中一个即可
                return loop1;
            }
            current1 = current1.next;
        }
        return null;
    }
}

以上是核心详细参考代码,相信主方法和测试方法能够轻易的写出来,当然想参考全部代码,此处也附上本文全部代码的Github地址:https://github.com/monday-pro/algorithm-study/blob/master/src/basic/node/FindFirstIntersectNode.java

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值