面试官问:怎么判断链表是否有环?看似简单我却当场想傻了!

链表是否有环问题看似简单,但实际处理上有很多需要注意的,这个问题是非常高频笔试面试题,记忆不牢固容易遗忘,可以认真看看学习一波!有个小伙伴就在某手面试中遇到了。

判断链表是否有环

题目描述:给定一个链表,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。

如果链表中存在环,则返回 true 。 否则,返回 false 。


image.png

你能用 O(1)(即,常量)内存解决此问题吗?

分析: 对于这个问题,如果没有内存空间的限制,首先想到的就是使用哈希的方法,用一个哈希存储节点,然后向下枚举链表节点:

如果发现其中有在哈希中,那么就说明有环返回true。
如果枚举到最后结束,那就说明没有环


image.png

但是这样并不满足O(1)空间复杂度的要求,我们应该怎么处理呢?

如果链表尾部有环,如果一个节点枚举到后面会在闭环中不断循环枚举,那么怎么样能高效判断有环并且能快速终止呢?

有环,其实就是第二次、第三次走过这条路才能说它有环,一个指针在不借助太多空间存储状态下无法有效判断是否有环(有可能链表很长、有可能已经在循环了),咱们可以借助 快慢指针(双指针) 啊。

其核心思想就是利用两个指针:快指针(fast)和慢指针(slow),它们两个同时从链表头遍历链表,只不过两者速度不同,如果存在环那么最终会在循环链表中相遇。

我们在具体实现的时候,可以快指针(fast)每次走两步,慢指针(slow)每次走一步。如果存在环的话快指针先进入环,慢指针后入环,在慢指针到达末尾前快指针会追上慢指针。

快慢指针如果有相遇那就说明有环,如果快指针先为null那就说明没环。

image.png

具体实现代码为:

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        ListNode fast=head;
        ListNode slow=fast;
        while (fast!=null&&fast.next!=null) {
            slow=slow.next;
            fast=fast.next.next;
            if(fast==slow)
                return true;
        }
        return false;    
    }
}

提高:找到环的入口位置

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。

说明:不允许修改给定的链表。

你是否可以使用 O(1) 空间解决此题?

这题相比上一题又难了一些,因为如果链表成环,需要找到入口。

分析:

如果不考虑内存使用,我肯定还会首先考虑哈希,将节点存着然后如果出现第二次则说明有环并直接返回,实现的代码也很简单,走投无路可以用这个方法:

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        int pos=-1;
        Map<ListNode,Integer>map=new HashMap<ListNode, Integer>();
        ListNode team=head;
        while (team!=null)
        {
            if(map.containsKey(team)){
                pos=map.get(team);
                return team;
            }
            else 
                map.put(team,++pos);
            team=team.next;
        }
        return null;
    }
}

但是怎么使用O(1)的空间复杂度完成这个操作呢?上面一题的思路是使用快慢指针判断是否有环,但是怎么锁定环的入口呢?

这个题看起来是个算法题,实际上是个数学推理题。这题的关键也是快慢指针,不过需要挖掘更多的细节 。

回忆一下快慢指针能够挖掘的细节:

知道慢指针走了x步,快指针走了2x步,但是仅仅知道这两个条件还推导不出什么东西,我们能够进行的操作也只有用O(1)的方法进行一些操作。不过这里面快慢指针和前面有点不同的是我们前面用一个头结点开始计数。

我们还可以进行什么操作?

既然知道相遇的这个点在环内,那么我们可以用一个新的节点去枚举一圈看看环的长度是多少哇!

这里面,我们可以知道fast走的步数2x,slow走的步数x,以及环长y。


image.png

我们知道,慢指针是第一次入环,但快指针可能已经走了好几圈,但是多走的步数一定是环的整数倍(不然不可能在同一个位置相遇)。

那么可以得到 快指针步数=慢指针步数+n圈环长度。当然这里n我暂时不知道是多少。换算成公式,那就是 2x=x+ny 消去一个x得到:x=ny。

上面的图我也标注快指针多走的是整数圈数。难点就在这里,需要变通:

快指针多走的x是环长y的整数倍n,慢指针走的x也是环长y的整数倍n。


image.png

那么这样有什么用呢?
如果某个节点从起点出发,走到fast,slow交汇点走的是x步(n*y步)。此时,如果某个指针从fast,slow交汇点开始如果走环长的整数倍,那么它到时候还会在原位置。

image.png

也就是说从开始head节点team1走x步,从fast,slow交汇节点team2走x步,它们最终依然到达fast,slow交汇的节点,但是在枚举的途中,一旦team1节点遍历的到环内,那么就和team2节点重合了,所以它们一旦相等那就是第一个交汇的点了。


image.png

实现代码为:

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        boolean isloop=false;
        ListNode fast=new ListNode(0);//头指针
        ListNode slow=fast;
        fast.next=head;
        if(fast.next==null||fast.next.next==null)
            return null;
        while (fast!=null&&fast.next!=null) {
            fast=fast.next.next;
            slow=slow.next;
            if(fast==slow)
            {
                isloop=true;
                break;
            }
        }
        if(!isloop)//如果没有环返回
            return null;
        ListNode team=new ListNode(-1);//头指针 下一个才是head
        team.next=head;
        while (team!=fast) {
            team=team.next;
            fast=fast.next;
        }
        return team;
    }
}

结语

到这里,链表找环问题就解决了,代码分析可能写<typo id="typo-3698" data-origin="的" ignoretag="true">的</typo>不够好,有问题还请指出,再接再厉!加油!

原文链接:https://blog.csdn.net/qq_40693171/article/details/117406085?spm=1001.2014.3001.5501

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,012评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,628评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,653评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,485评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,574评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,590评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,596评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,340评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,794评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,102评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,276评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,940评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,583评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,201评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,441评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,173评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,136评论 2 352

推荐阅读更多精彩内容