剑指offer | 面试题23:链表中环的入口结点

转载本文章请标明作者和出处
本文出自《Darwin的程序空间》
本文题目和部分解题思路来源自《剑指offer》第二版

在这里插入图片描述

开始行动,你已经成功一半了,献给正在奋斗的我们

题目

一个链表中包含环,如何找出环的入口结点?如图一,环的入口节点是3
在这里插入图片描述

(图一)

解题分析

这道题,首选用我们程序员的思维抽象一下,其实就是在遍历链表的时候找到第一个重复节点,这个节点就是入环点,如果遍历的过程中,写一个节点是空的话,就说明这个链表不成环;

如果上面说的,你也想到了的话,那么你一定会先想到使用哈希表(HashMap),我们把每次遍历的节点都判断哈希表中有没有,没有的话放到哈希表中,有的话,这个节点就是入环点;这样做,我们只需要遍历一遍链表,并且因为哈希表的查询时间复杂度可以看做O(1),所以我们最终的时间复杂度只需要O(n),也就是遍历链表所需要的时间复杂度,这样看下来,这应该是最优解法,但是,这种解法却也用了O(n)的空间复杂度,因为原则上,每一个元素我们都要放到哈希表中一次;

所以我们可问下面试官,这个对空间复杂度有没有什么要求,如果要求空间复杂度是常数阶的话,那么你就需要考虑更深层次的算法了;

相信大家在学生时代应该都跑过步,操场一般都是圆的,那么,一个跑的快的同学,和一个跑的慢的同学一直在上面跑,快的一定会扣慢的圈,对么?


这就引申出来第一个问题,我们怎么判断这个链表有没有环;

首先我们可以定义一个快节点的引用fast,和一个慢节点的引用slow(两个同学,一个跑的快的,一个跑的慢的),两个节点同时指向链表的头节点,然后等待开始,枪响之后,两个节点同时向前移动,快的一次移动两个节点,慢的移动一个节点。基于上面的道理,如果链表有环,快的就一定会在圆环上追上慢的,实则就是扣圈了。在程序上表现来说就是,fast == slow,这时两个引用指向同一个节点;如果链表没有环,快的在移动的过程中就一定会遇到前方没有路的情况,也就是下一节点为空(但记住,快节点fast一次动两个节点,所以要判断下一节点和下下一节点是否为空);

如果题目是判断链表是否有环,上面的就是最优的答案;然而问题是入环的节点,如果快节点正好在入环处追上慢节点,那么这就是正确答案;然而,大多数时候二者是在环内相遇;

到这,我们需要再明确一件事情,为什么快节点一次要移动两个节点,为什么不是三个、四个,这样不是更快到终点么?如果这样的话,你想过没有,万一慢节点正好被快节点跨过去了呢?拿图一举例,假设某一时间快节点处在节点3的位置,慢节点在节点4,那么下一次移动快的到了6慢的到了5,没有遇到,尴尬不?所以我们要明确,每一次移动,快节点都离慢节点近了一个节点;

我们在来分析一下快慢节点算出的相遇点在什么位置,假设,头节点到入环点的距离是n(以图一来说1到3的距离就为n),环的长度为k,那么当慢节点走到入环点,快节点一定在慢节点前方n节点处。从这一刻开始,我们看做快的在追慢的话,那么快节点还要追的距离就为k - n;也就是说,快节点会在距离入环点k - n的距离追上慢节点。这时,快慢节点距离入环点的距离均为n,那么这时把快的放到链表的头节点,也是节点1处,并让他们都按速度1来走,移动n次之后,就一定会在入环点相遇(切记让快的放慢速度);但是如果k - n等于0(也是就开始节点即入环点,跑了一圈才发现开始的地方才是目的地),在这么做就不对了,所以要判断一下这是链表是不是一整个环,就是在第一次相遇的时候看看相遇的节点是不是头节点即可。

代码(JAVA实现)

ps:这里笔者使用的jdk为1.8版本

上面的代码是用了一个循环搞定的,所以在循环里面要判断快慢节点第一次相遇(就是刚起跑)的情况,不能算在内,第二次相遇如果是圆环的话,就是答案,如果不是的话,就要第三次相遇才是入环点;如果读者理解起来有问题的话,可以先找到第一个相遇点,判断不是圆环之后再找第二个就是答案;我把这种写法的代码放到下面,大家可以任意选择;

  • 一个循环
public class EntryNodeOfLoop {

    public static void main(String[] args) {
        ListNode l1 = new ListNode(1);
        ListNode l2 = new ListNode(2);
        ListNode l3 = new ListNode(3);
        l1.next = l2;
        l2.next = l3;
        l3.next = l1;
        System.out.println(entryNodeOfLoop(l1).val);
    }

    public static ListNode entryNodeOfLoop(ListNode pHead) {
        if (Objects.isNull(pHead)) {
            return null;
        }
        ListNode fast = pHead;
        ListNode slow = pHead;

        int count = 0;
        while (true) {
            if (Objects.isNull(fast.next) || Objects.isNull(fast.next.next)) {
                  return null;
            }
            if (fast == slow) {
                if (count == 1) {
                    if (fast == pHead) {
                        return fast;
                    }
                    fast = pHead;
                    count ++;
                } else if (count == 2) {
                    return fast;
                } else {
                    count++;
                }
            }
            if (count == 1) {
                fast = fast.next.next;
            } else {
                fast = fast.next;
            }
            slow = slow.next;
        }
    }
}
  • 两个循环
public class Solution {
    public ListNode detectCycle(ListNode head) {
        if (head == null || head.next == null) {
            return null;
        }

        ListNode slow = head;
        ListNode fast = head;

        boolean first = true;

        while (first || slow != fast) {

            if (fast == null || fast.next == null) {
                return null;
            }
            fast = fast.next.next;
            slow = slow.next;
            first = false;
        }

        fast = head;

        while (fast != slow) {
            fast = fast.next;
            slow = slow.next;
        }

        return fast;
    }
}
喜欢的朋友可以加我的个人微信,我们一起进步
发布了156 篇原创文章 · 获赞 19 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/qq_36929361/article/details/104367817