【初阶数据结构】单链表之环形链表

目录标题

  • 前言
  • 环形链表的约瑟夫问题
  • 环形链表
  • 环形链表||

请添加图片描述

前言

  前面我们已经学习了关于单链表的一些基本东西,今天我们来学习单链表的一个拓展——环形链表,我们将用力扣和牛客网上的三道题目来分析讲解环形链表问题。

环形链表的约瑟夫问题

  我们首先来看一道非常经典的题目——环形链表的约瑟夫问题。题目问题如下:

在这里插入图片描述
输入:5,2
输出:3
说明:
开始5个人 1,2,3,4,5 ,从1开始报数,1->1,2->2编号为2的人离开
1,3,4,5,从3开始报数,3->1,4->2编号为4的人离开
1,3,5,从5开始报数,5->1,1->2编号为1的人离开
3,5,从3开始报数,3->1,5->2编号为5的人离开
最后留下人的编号是3

  看着问题好像无从下手,但只要掌握了链表的相关知识,你也能拿捏环形链表题目。
  这道题目说将n个人围城一个圈,转换成链表也就是将n个节点串起来变成一个圈,即将最后一个节点的next指针指向头节点,这样环形链表就出现了。
  当有5个人,编号为2的离开,流程如下
在这里插入图片描述

在这里插入图片描述

  代码实现(解析包含在代码中)

/**
 * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
 *
 * 
 * @param n int整型 
 * @param m int整型 
 * @return int整型
 */
 
//题目中没有给出链表,我们首先要自己创建一个链表
 typedef struct ListNode ListNode;//给节点重命名
 ListNode* buyNode(int x)//建立新节点
 {
    ListNode* node = (ListNode*)malloc(sizeof(ListNode));
    if(node == NULL)
    {
        exit (1);
    }
    node->val = x;
    node->next = NULL;
    return node;

 }
 ListNode* createCircle(int n)
 {
    ListNode* phead = buyNode(1);//首先创建头节点,使后面的节点都能相连
    ListNode* ptail = phead;
    for(int i = 2;i <= n;i++)
    {
        ptail->next = buyNode(i);
        ptail = ptail->next;
    }
    ptail->next = phead;//尾节点的next指向头节点,使其成为环形链表
    return ptail;//返回尾节点
 }
int ysf(int n, int m ) {
    // write code here
    ListNode* prev = createCircle(n);
    ListNode* pcur = prev->next;//让尾节点的下一个节点也就是头节点赋给pcur,让其成为数数的起始点。
    int count = 1;//开始第一次数数记为1
    while(pcur->next != pcur)//结束的标志是pcur->next == pcur,此时就只剩pcur这一个节点,则跳出循环
    {
        if(count == m)//如果pcur所对应的这个节点的值恰好为m,则要删除这个节点,在单链表中我们也讲过删除节点要怎么删除,便不再多讲
        {
            prev->next = pcur->next;
            free(pcur);
            pcur = prev->next;
            count = 1;//pcur往下走了一个节点要记得把count置为1,继续下一次循环
        }
        else //不为m时,prev指针和pcur指针都往下走一个,且pcur对应的报数count要加加
        {
            prev = pcur;
            pcur = pcur->next;
            count ++;
        }
    }
    return pcur->val;
}

环形链表

  学会的环形链表的约瑟夫问题,下面来两道力扣中的环形链表问题
  题目链接如下:环形链表。题目如下:
在这里插入图片描述
在这里插入图片描述
  我们要判断一个链表看其是否有环,先说结论,我们可以利用快慢指针进行求解。顾名思义,快慢指针就是存在两个指针,一个走的快,一个走的慢,快指针一次走走两步或三步四步都可以,慢指针一般走一步。
  这道题中,我们让快指针一次性走两边,慢指针一次性走一步,当慢指针进入环以后,快指针开始追慢指针,如果快指针能追上慢指针,也就是两指针相遇,则证明该链表带环。 代码如下:

/**
 1. Definition for singly-linked list.
 2. struct ListNode {
 3.     int val;
 4.     struct ListNode *next;
 5. };
 */
typedef struct ListNode ListNode; 
bool hasCycle(struct ListNode *head) {
    ListNode* slownode = head, *quicknode = head;//定义快慢指针
    while(quicknode && quicknode->next)//快指针以及快指针指向的下一个节点不为空,则一直循环
    {
        slownode = slownode->next;//慢指针一次走一步
        quicknode = quicknode->next->next;//快指针一次走两步
        if(quicknode == slownode)//快慢指针相遇,则链表带环
        {
            return true;
        }
    }
    return false;//为空跳出循环说明该链表不带环
}

下面将解决大家的疑惑点:

  1. 为什么快慢指针一定会相遇,而不是会错过,如何证明
  2. 当慢指针走一步,快指针走3步,4步……又一定能追上相遇吗,如何证明

  这两个问题我们来一一解决。
  首先是第一个问题:为什么快慢指针一定会相遇,而不是会错过,如何证明。我们可以将快慢指针走的过程图画出来
在这里插入图片描述
  所以当快指针一次走两步,慢指针一次走一步时,如果链表带环,二者一定会在环内相遇,这就证明解决了第一个问题:
  下面是第二个问题:当慢指针走一步,快指针走3步,4步……又一定能追上相遇吗,如何证明。
  当慢指针一次走一步,快指针一次走3步时,证明如下:

当slow进环时,fast和slow的距离为n
slow一次走一步,fast一次走三步
则它们每追击一次,距离缩小2,

n是偶数n是奇数
nn
n-2n-2
…………
43
21
0-1
追上了错过了,进行新一轮追击

如果n是奇数,fast错过了slow,此时fast到了slow前面一格,假设环的长度为C,则slow和fast是距离变为了C-1,开始新一轮追击,还是讨论C-1是偶数还是奇数的问题,如果C-1是偶数,就能追上,如果C-1是奇数,那永远不会追上。

根据以上分析我们可以得出结论:

  • 当n时偶数,第一轮就能追上
  • 当n时奇数时,第一轮追击会错过,二者的距离变成C-1(C是环的长度)
    • 如果C-1是偶数,即C是奇数,那么下一轮就能追上相遇
    • 如果C-1是奇数,即C是偶数,那么永远追不上

看似我们已经讨论出了有追上和追不上的这两种情况,但是追不上的情况:即n是奇数C是偶数的情况真的会存在吗,我们可以继续往下讨论:
在这里插入图片描述

当slow进环时,fast和slow的距离为n
slow一次走一步,fast一次走三步
我们就能发现fast走的距离是slow的三倍
根据这个关系可以列出等式
slow走的距离:L
fast走的距离:L+x * c+c-n(slow进环时,假设fast已经在环里面转了n圈)
则得到等式:3 * L = L+x * c+c-n
化简得到:2 * L = (x+1) * c-n
2*L必定是偶数,
根据上面得出的结论:当n是奇数,c是偶数时,永远追不上
将其带入右边的式子,得到:(x+1)*偶数 - 奇数,此时得到的式子只能是奇数,与左边不等,则发现永远追不上的条件不成立。

  根据以上一系列分析,可以得出结论:
  当slow一次走一步,fast一次走三步时,若n是偶数,则第一轮就能追上,若n是奇数,c是奇数时,第二轮追上,即无论如何,一定能相遇追上。当fast一次走4步或者5步时也是同样的证明方法。

环形链表||

  这道题的环形链表是上一道题的进阶,如果链表中有环,则要返回入环的第一个节点,没环则返回空,题目链接如下:环形链表||
在这里插入图片描述
  对于本题,我们用到的是和上一题差不多一样的思路,首先判断有没有环,就用快慢指针,发现有环,要找到入环的第一个节点,我们有两种办法解决:

  1. 将快慢指针相遇时的节点我们记为meet节点,然后让头节点和meet节点同时走,每次都走一步,当二者相遇时的节点就是进环时的第一个节点。
  2. 将快慢指针相遇时的节点我们记为meet节点,然后将meet节点与meet的下一个节点断开,使其形成两条链,此时就变成了求两条链表的相交问题(代码写起来较为复杂)

下面一一讲解:

对于方法一:
我们需要证明的是:为什么让头节点和meet节点一起每次走一步,当二者相遇的时候就是进环节点。
上一题我们通过画图找等式来解决为什么一定相遇,这题我们同样可以通过画图找等式解决。
在这里插入图片描述
我们用快慢指针,快指针一次走两步,慢指针一次走一步
则快指针走的路程是慢指针的两倍
由图,slow和fast相遇时距离进环时的一个节点为N:
slow走的距离:L+N (slow指针在环内走不到一整圈,因为当slow走一圈,fast在环内走两圈,肯定早就会相遇,所以slow指针在环内只能走N的距离
fast走的距离:L+x * C +N (slow进环前,fast指针已经走了x圈)
所以得到等式:2 * (L+N) = L + x * C + N
化简得到:L = (x - 1)*C + C - N
(x - 1)*C是圈数
C - N 是meet节点与进环节点的距离,二者相加刚好为L
这就是为什么头结点和meet节点每次同时走一步,二者相遇时就是进环节点

代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode ListNode;
struct ListNode *detectCycle(struct ListNode *head) {
    ListNode* fast = head, *slow = head;//给定两个快慢指针
    while(fast && fast->next)
    {
        fast = fast->next->next;
        slow = slow->next;
        if(fast == slow)//相遇则带环
        {
            ListNode* phead = head;
            ListNode* meet = slow;//相遇节点置为meet
            while(phead != meet)
            {
                phead = phead->next;
                meet = meet->next;//头节点和meet节点每次走一步
            }
            return phead;//相遇,返回其节点
        }
    }
    return NULL;//不带环返回空
    
}

对于方法二:
我们要知道如何把环形链表断开,使其成为两条链表,并找到两条链表的相交节点,即为进环节点。
在这里插入图片描述
将meet的下一个节点记为newhead,并将其断开,此时就有head和newhead两条链,求这两条链表的相交节点即可。

代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode ListNode;
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {//定义一个函数,寻找两条链表的相交节点
    ListNode* l1 = headA, *l2 = headB;
    int a_count = 1, b_count = 1;
    while(l1->next)
    {
        l1 = l1->next;
        a_count++;//给A链表长度计数
    }
    while(l2->next)
    {
        l2 = l2->next;
        b_count++;//给B链表长度计数
    }
    //此时l1和l2都到达了两条链表的尾节点
    if(l1 != l2)//如果二者不相等,说明两条链表不想交
    {
        return NULL;
    }
    //到这,说明两条链表尾节点相等,链表相交
    int gap = abs(a_count - b_count);//得到两链表节点个数的差值
    ListNode* fastnode = headA, *slownode = headB;//假定长链表为链表A,短链表为链表B
    if(b_count > a_count)//如果B链表节点数多则交换
    {
        fastnode = headB;
        slownode = headA;
    }//就能保证fastnode一定是长的那个链表
    while(gap)
    {
        fastnode = fastnode->next;//先让长的链表走它们的差值
        gap--;
    }
    while(fastnode != slownode)
    {
        fastnode = fastnode->next;
        slownode = slownode->next;//两链表一起走,相等了则为相交节点
    }
    return fastnode;
}
struct ListNode *detectCycle(struct ListNode *head) {
    ListNode* fast = head, *slow = head;
    while(fast && fast->next)
    {
        fast = fast->next->next;
        slow = slow->next;
        if(fast == slow)
        {
            ListNode* phead = head;
            ListNode* meet = slow;//相遇节点置为meet
            ListNode* newhead = meet->next;//meet节点的下一节点作为新链表的头结点newhead
            meet->next = NULL;//将其断开
            return getIntersectionNode(phead,newhead);//返回两条链表的相交节点。
            
        }
    }
    return NULL;
    
}

  方法二就到此结束,代码比较长是因为设计到了两条链表相加节点的问题,在力扣上也有此题目,大家也可以下去做做,链接如下:相交链表。
  本篇内容到此结束了,关于链表大家要自己多想多画图多练习。感谢大家观看,如果大家喜欢,希望大家一键三连支持一下,如有表述不正确,也欢迎大家批评指正。
请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/600480.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Three.js基础学习】15.scroll-based-animation

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 前言 课程要点 结合html等场景 做滚动动画 1.遇到的问题&#xff0c; 在向下滚动时&#xff0c;下方会显白&#xff08;部分浏览器&#xff09; 解决&#xff1a;alpha:true …

【网络编程】http协议

预备知识 什么是http协议 HTTP&#xff08;Hypertext Transfer Protocol&#xff0c;超文本传输协议&#xff09;是一个应用层的协议&#xff0c;用于在网络中传输超文本&#xff08;如HTML文档&#xff09;。HTTP协议建立在TCP/IP协议之上&#xff0c;是Web浏览器和Web服务器…

「JavaEE」线程安全2:内存可见性问题 wait、notify

&#x1f387;个人主页&#xff1a;Ice_Sugar_7 &#x1f387;所属专栏&#xff1a;JavaEE &#x1f387;欢迎点赞收藏加关注哦&#xff01; 内存可见性问题& wait、notify &#x1f349;Java 标准库的线程安全类&#x1f349;内存可见性问题&#x1f34c;volatile 关键字 …

自动化运维管理工具----------Ansible模块详细解读

目录 一、自动化运维工具有哪些&#xff1f; 1.1Chef 1.2puppet 1.3Saltstack 二、Ansible介绍 2.1Ansible简介 2.2Ansible特点 2.3Ansible工作原理及流程 2.3.1内部流程 2.3.2外部流程 三、Ansible部署 3.1环境准备 3.2管理端安装 ansible 3.3Ansible相关文件 …

DirClass

DirClass 通过分析&#xff0c;发现当接收到DirClass远控指令后&#xff0c;样本将返回指定目录的目录信息&#xff0c;返回数据中的远控指令为0x2。 相关代码截图如下&#xff1a; DelDir 通过分析&#xff0c;发现当接收到DelDir远控指令后&#xff0c;样本将删除指定目录…

java入门详细教程——day01

目录 1. Java入门 1.1 Java是什么&#xff1f; 1.2 Java语言的历史 1.3 Java语言的分类 1.4 Java语言的特点 1.4.1 先编译再解释运行 1.4.2 跨平台 1.5 JRE和JDK&#xff08;记忆&#xff09; 1.6 JDK的下载和安装&#xff08;应用&#xff09; 1.6.1 下载 1.6.2 安…

Redis(持久化)

文章目录 1.RDB1.介绍2.RDB执行流程3.持久化配置1.Redis持久化的文件是dbfilename指定的文件2.配置基本介绍1.进入redis配置文件2.搜索dbfilename&#xff0c;此时的dump.rdb就是redis持久化的文件3.搜索dir&#xff0c;每次持久化文件&#xff0c;都会在启动redis的当前目录下…

智能实训-wheeltec小车-抓取(源代码)

语言 :C 源代码&#xff1a; #include <ros/ros.h> #include <image_transport/image_transport.h> #include <cv_bridge/cv_bridge.h> #include <sensor_msgs/image_encodings.h> #include <sensor_msgs/JointState.h> #include <geometry…

leetcode17. 电话号码的字母组合

题目描述&#xff1a; 给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何字母。 示例 1&#xff1a; 输入&#xff1a;digits "…

产品需求文档怎么写?超详细的产品需求文档PRD模板来了!

产品需求文档怎么写&#xff1f;如何写一份简洁明了、外行人看了就能秒懂的产品需求文档呢&#xff1f;今天这篇文章&#xff0c;就来和大家分享如何编写一份高质量的产品需求文档 PRD&#xff01; 下图是来自 boardmix 模板社区的「产品需求文档」模板&#xff0c;它给出了一…

Verilog刷题笔记47

题目&#xff1a; From a 1000 Hz clock, derive a 1 Hz signal, called OneHertz, that could be used to drive an Enable signal for a set of hour/minute/second counters to create a digital wall clock. Since we want the clock to count once per second, the OneHer…

SpringBoot+Vue+Element-UI实现在线外卖系统

前言介绍 随着科学技术的飞速发展&#xff0c;各行各业都在努力与现代先进技术接轨&#xff0c;通过科技手段提高自身的优势社会的发展和科学技术的进步&#xff0c;互联网技术越来越受欢迎。网络计算机的生活方式逐渐受到广大人民群众的喜爱&#xff0c;也逐渐进入了每个用户的…

【计算机科学速成课】笔记三——操作系统

文章目录 18.操作系统问题引出——批处理设备驱动程序多任务处理虚拟内存内存保护Unix 18.操作系统 问题引出—— Computers in the 1940s and early 50s ran one program at a time. 1940,1950 年代的电脑&#xff0c;每次只能运行一个程序 A programmer would write one at…

北京大学-知存科技存算一体联合实验室揭牌,开启知存科技产学研融合战略新升级

5月5日&#xff0c;“北京大学-知存科技存算一体技术联合实验室”在北京大学微纳电子大厦正式揭牌&#xff0c;北京大学集成电路学院院长蔡一茂、北京大学集成电路学院副院长鲁文高及学院相关负责人、知存科技创始人兼CEO王绍迪、知存科技首席科学家郭昕婕博士及企业研发相关负…

vivado Versal ACAP 可编程器件镜像 (PDI) 设置

Versal ACAP 可编程器件镜像 (PDI) 设置 下表所示 Versal ACAP 器件的器件配置设置可搭配 set_property <Setting> <Value> [current_design] Vivado 工具 Tcl 命令一起使用。 注释 &#xff1a; 在 Versal ACAP 架构上 &#xff0c; 原先支持将可编程器…

408算法题专项-2009年

题目&#xff1a; 分析&#xff1a;09年的链表题目比较简单&#xff0c;直接构建链表&#xff0c;然后根据不同思路模拟即可。 思路一&#xff1a;循环遍历 思考&#xff1a;最容易想到的思路&#xff0c;直接暴力循环。偷了一下懒&#xff0c;变量名称没用题目的&#xff0c;…

力扣每日一题105:从前序与中序序列构造二叉树

题目 给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树并返回其根节点。 示例 1: 输入: preorder [3,9,20,15,7], inorder [9,3,15,20,7] 输出: [3,9,20,null,null,1…

语音识别--kNN语音指令识别

⚠申明&#xff1a; 未经许可&#xff0c;禁止以任何形式转载&#xff0c;若要引用&#xff0c;请标注链接地址。 全文共计3077字&#xff0c;阅读大概需要3分钟 &#x1f308;更多学习内容&#xff0c; 欢迎&#x1f44f;关注&#x1f440;【文末】我的个人微信公众号&#xf…

硬盘惊魂!文件夹无法访问怎么办?

在数字时代&#xff0c;数据的重要性不言而喻。然而&#xff0c;有时我们会遇到一个令人头疼的问题——文件夹提示无法访问。当你急需某个文件夹中的文件时&#xff0c;却被告知无法打开&#xff0c;这种感受真是难以言表。今天&#xff0c;我们就来深入探讨这个问题&#xff0…

第六代移动通信介绍、无线网络类型、白皮书

关于6G 即第六代移动通信的介绍&#xff0c; 图解通信原理与案例分析-30&#xff1a;6G-天地互联、陆海空一体、全空间覆盖的超宽带移动通信系统_6g原理-CSDN博客文章浏览阅读1.7w次&#xff0c;点赞34次&#xff0c;收藏165次。6G 即第六代移动通信&#xff0c;6G 将在5G 的基…