寻找单向链表中含有环的入口节点

这是一个比较基本的链表问题。总结一下,可以扩展为以下几类问题:

  • 判断链表中是否有环
  • 求链表中环的长度
  • 求环的入口节点的位置
  • 带环链表的长度

解法:

  • 判断是否有环:
    • 可以利用快慢指针追赶的方式,即设定两个指针,slow、fast,slow指针每次走一步,fast指针每次走两步,如果存在环,则两个指针会相遇,即slow.val==fast.val;如果不存在环,则fast指针会先于slow到达链表结尾,此时,fast 应该为null,循环结束。
    • 为什么有环的情况下二者一定会相遇:
      • 因为fast会先于slow进入环,在slow进入环之后,可以把slow看作在前面,如果slow在前面,fast在后面每次循环都会向slow靠近1,所以一定会相遇,而不会出现fast直接跳过slow的情况。

具体代码如下:
定义ListNode数据结构

public class ListNode {

    public int val;
    public ListNode next;

    public ListNode(int x) {
        val = x;
    }
/**
     * 判断单向链表是否有环
     * @param pHead
     * @return
     */
    public boolean HasLoopInListNode(ListNode pHead){
        if(pHead == null ||pHead.next ==null){
            return false;
        }
        ListNode slow = pHead;
        ListNode fast = pHead;
        while(slow !=null&&fast.next !=null){
            //快慢指针分别访问下一节点
            slow = slow.next;
            fast = fast.next.next;
            if(fast.val == slow.val)
                return true;
        }
        return false;
    }
  • 求环的长度
    • 对于求环的长度,可以在第一问的基础上进行进一步的操作,即记录下快慢指针slow、fast首次相遇的节点P,然后slow从P点开始,再次访问环中下一节点,而fast停留在碰撞点P,直到slow再一次到达碰撞点P,slow走过的步数即为环的长度。
/**
     * 求环的长度
     * @param pHead
     * @return
     */
    public int LengthOfLoopListNode(ListNode pHead){
        if(pHead == null ||pHead.next ==null){
            return 0;
        }
        ListNode slow = pHead;
        ListNode fast = pHead;

        //记录快慢指针第一次相遇之后为起点,直至第二次相遇所走过的步长
        int step = 0;
        while(slow !=null&&fast.next !=null){
            slow = slow.next;
            fast = fast.next.next;
            if(fast.val == slow.val){
                break;
            }
        }
        if(fast==null||fast.next == null){
            return step;
        }
        //此时,fast停留在首次碰撞的节点
        while(fast.val != slow.next.val){
            step++;
            slow = slow.next;
        }
        return step;
    }
  • 求环的连接点在哪里
    • 关于求连接点的问题,可以在第二问的基础上再进一步分析,因为在第二问求解过程中,记录了fast与slow首次相遇的节点P,可称之为碰撞点P(点P可能是在环的内部,也可能正好在连接点)。碰撞点P到环连接点的距离,就是头指针到连接点的距离。定理证明如下:
    • 链表结构示意图
      假设某含有环的链表如上图所示,X为头结点,Y为环的连接点,Z为快慢指针首次相遇的点。
      则在快慢指针首次相遇时,
      • slow走过的距离:L1 = a+b;
      • fast 走过的距离:L2 = a+b+c+b。

又因为fast的速度是slow的两倍,所以fast走过的距离是slow走过的两倍,所以,L2 = 2*L1。即有2(a+b) = a+b+c+b。很容易得出,a = c,也就是说,头指针到连接点的距离,就是快慢指针首次相遇的碰撞点到连接点的距离。
所以在求连接点位置这个问题时,已经在第二问中求出了碰撞点P的位置(图中为Z),则可以让slow从头结点开始,fast从碰撞点Z开始,每次分别走一步,直至相遇,因为a=c,所以相遇时,一定在连接点Y处

/**
     * 找出连接点
     * @param pHead
     * @return
     */
    public ListNode getFirstJoin(ListNode pHead){
        if(pHead == null ||pHead.next ==null){
            return null;
        }
        ListNode slow = pHead;
        ListNode fast = pHead;
        //找出第一次碰撞的点
        while(slow !=null&&fast.next !=null){
            slow = slow.next;
            fast = fast.next.next;
            if(fast.val == slow.val){
                break;
            }
        }
        slow = pHead;
        while(slow.val != fast.val){
            slow = slow.next;
            fast = fast.next;
        }
        return slow;
    }
  • 求带环链表长度
    • 此问稍作分析即可得到带环链表长度L =a+ b+c
    • 因为在第三问中,可以设置一个变量Len记录头结点到连接点的距离,换长度可以在第二问中求出。
/**
     * 求带环链表长度
     * @param pHead
     * @return
     */
    public int getLoopLength(ListNode pHead){
        if(pHead == null ||pHead.next ==null){
            return 0;
        }
        ListNode slow = pHead;
        ListNode fast = pHead;
        //头结点到连接点的距离
        int LengthToJoin = 0;
        //环长度
        int LengthOfLoop = LengthOfLoopListNode(pHead);
        //找出第一次碰撞的点
        while(slow !=null&&fast.next !=null){
            slow = slow.next;
            fast = fast.next.next;
            if(fast.val == slow.val){
                break;
            }
        }
        slow = pHead;
        while(slow.val != fast.val){
            LengthToJoin++;
            slow = slow.next;
            fast = fast.next;
        }

        return LengthOfLoop+LengthToJoin;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值