查看原文
其他

漫画:不一样的链表成环检测!

浩仔 林小浩 2022-05-13

今天为大家带来,链表检测成环的经典题目。如果你觉得你会了,请你不妨耐心些认真看下去,我相信会有一些不一样的收获!


先看题目:

01第141题:环型链表

第141题:给定一个链表,判断链表中是否有环。为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。

示例 1:

输入:head = [3,2,0,-4], pos = 1输出:true解释:链表中有一个环,其尾部连接到第二个节点。

示例 2:

输入:head = [1,2], pos = 0输出:true解释:链表中有一个环,其尾部连接到第一个节点。

示例 3:

输入:head = [1], pos = -1输出:false解释:链表中没有环。


题目可能你会觉得过于简单!

但是不妨耐心看完!

则一定会有收获!


02题解一:哈希表判定

思路:通过hash表来检测节点之前是否被访问过,来判断链表是否成环。这是最容易想到的一种题解了。过于简单,直接上代码:


1//go
2func hasCycle(head *ListNode) bool {
3    m := make(map[*ListNode]int)
4    for head != nil {
5        if _, exist := m[head]; exist {
6            return true
7        }
8        m[head] = 
1 9        head = head.Next
10    }
11    return false12}


03

题解二:JS特殊解法



相信对于JS中的 JSON.stringify() 方法大家都用过,主要用于将JS对象 转换为 JSON 字符串。基本使用如下:


1//js
2var car = {
3  name'小喵',
4  age20,
5}
6
7var str = JSON.stringify(car);
8
9console.log(str) 
10//=> {"name":"小喵","age":20}


大家想一下,如果是自己实现这样的一个函数,我们需要处理什么样的特殊情况?对,就是循环引用。因为对于循环引用,我们很难通过 JSON 的结构将其进行展示!比如下面:


1//js
2var a = {}
3
4var b = {
5  a: a
6}
7a.b = b
8
9console.log(JSON.stringify(a))
10//=> TypeError: Converting circular structure to JSON


那我们思考,对于环形链表,是不是就是一个循环结构呢?当然是!因为只要是环形链表,它一定存在类似以下代码:


a.Next = b

b.Next = a


所以我们可以通过JSON.stringify()的特性进行求解:


1//js
2var hasCycle = function(head) {
3    try{
4        JSON.stringify(head)
5    }catch(e){
6        return true
7    }
8    return false
9};


当然,这种解法并不是建议的标准题解!在此列出是为了拓宽思维!(大家如有兴趣,可以自己去看下JSON.stringify内部的实现,是如何检测循环引用的。)

03题解三:双指针解法

本题标准解法!常识内容,必须掌握


思路来源:先想象一下,两名运动员以不同速度在跑道上进行跑步会怎么样?相遇!好了,这道题你会了。


解题方法:通过使用具有 不同速度 的快、慢两个指针遍历链表,空间复杂度可以被降低至 O(1)。慢指针每次移动一步,而快指针每次移动两步。


假设链表为   , 其步骤如下:



分析完毕,直接上代码:


1func hasCycle(head *ListNode) bool {  
2    if head == nil {
3        return false
4    }
5    //有些同学不理解为什么初始化快指针在head.Next的位置
6    //我们可以认为是从起跑线开始的!
7    fast := head.Next  
8    for fast != nil && head != nil && fast.Next != nil {
9        if fast == head {   // 快慢指针相遇,今生的擦肩而过
10            return true
11        }
12        fast = fast.Next.Next  
13        head = head.Next   
14    }
15    return false
16}


这里我们要特别说明一下,为什么慢指针的步长设置为1,而快指针步长设置为2。


首先,慢指针步长为1,很容易理解,因为我们需要让慢指针步行至每一个元素。而快指针步长为2,通俗点可以理解为他们的相对速度只差1,快的只能一个一个格子的去追慢的,必然在一个格子相遇。


如果没看懂,我们来分析:在快的快追上慢的时,他们之间一定是只差1个或者2个格子。如果落后1个,那么下一次就追上了。如果落后2个,那么下一次就落后1个,再下一次就能追上!如下图:



所以我们的快指针的步长可以设置为2。


04特别说明


我们常会遇到一些所谓的“简单题目“,然后用着前人留下来的那些”经典题解“迅速作答。在解题的过程中,追求公式化、模板化。当然,这个过程是好的,因为社会、工作、学业要求我们如此!但是,我希望我们也可以留下一些自己的思考,纵然不是最优解,但是是我们自己想到的、创造的!真正在算法题中去收获快乐~



注:本系列所有教程中都不会用到复杂的语言特性,大家不需要担心没有学过语法知识。算法思想最重要,使用各语言纯属本人爱好。同时,本系列所有代码均在leetcode上进行过测试运行,保证其严谨性!



温馨提示



浩仔讲算法~

每天一起学习图解漫画算法。

一起刷题,一起成长!

~长按下方二维码进行关注吧~

关注后有资源~



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存