快慢指针及其应用

快慢指针及其应用算法学习笔记 快慢指针及其应用 前言一 什么是快慢指针二 快慢指针的应用 1 引入库 2 读入数据总结前言 nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp 计算机算法是一门非常有意思的科学 经常研究算法能激发我们大脑的潜能 使我们的大脑变得越来越灵活 思考问题变得更加敏捷和全面 本文是我学习快慢指针相关知识的一个读书笔记 包含快慢指针的应用例子 及快慢指针在这个例子中的应用为什么会正确的数学证明 在算法方面 我也是一个初学者 以前也没有深入的 专门的


前言

         计算机算法是一门非常有意思的科学,经常研究算法能激发我们大脑的潜能,使我们的大脑变得越来越灵活,思考问题变得更加敏捷和全面。本文是我学习快慢指针相关知识的一个读书笔记,包含快慢指针的应用例子,及快慢指针在这个例子中的应用为什么会正确的逻辑推导。在算法方面,我也是一个初学者,以前也没有深入的,专门的研究过,如果文章中有错误的地方,希望留言指正,大家一同学习,共同进步。


提示:以下是本篇文章正文内容,下面案例可供参考

一、什么是快慢指针

         快慢指针中的快慢指的是移动的步长,即每次向前移动速度的快慢。例如可以让快指针每次沿链表向前移动两步,慢指针每次向前移动一步。快慢指针的定义就那么简单,后面会给出一个例子:利用快慢指针判断单列表是否有环,查找环的入口节点,以及使用数学方程式证明查找方式的正确性。

二、快慢指针的应用

1.题目描述

给定一个链表,如果有环路,找出环路的开始点。

2.输入输出样例

         输入是一个链表,输出是链表的一个节点。如果没有环路,返回一个空指针。.

在这里插入图片描述

         在这个样例中,值为 2 的节点即为环路的开始点。链表节点定义如下:

struct ListNode { 
      int val; ListNode *next; ListNode(int x) : val(x), next(nullptr) { 
     } }; 

3.解题思路

         对于链表是否存在环的问题,有通用的解决方法,即快慢指针。定义两个指针,快指针命名为fast,慢指针命名为slow,它们同时指向链表的开头。快指针每次前进两步,慢指针每次前进一步。如果快指针能走到尽头,则链表没有环;如果快指针无限走下去,且某一时刻和慢指针相遇,那么这个链表一定有环。
         上面的思路我们可以断定链表是否有环,那么,环的入口节点应该如何确定?这个很简单,当快慢节点首次相遇后,我们把快指针指向链表开头,且每次只前进一步;慢指针继续从相遇点开始,保持每次前进一步,那么,当快慢指针第二次相遇时,相遇的节点即为环的入口节点。

4.代码实现

struct ListNode { 
      int val; ListNode* next; ListNode(int x) : val(x), next(nullptr) { 
     } }; ListNode* find_enter_of_cycle(ListNode* head) { 
      ListNode* slow = head; ListNode* fast = head; // 第一步:判断是否有环 do { 
      if (fast == nullptr || fast->next == nullptr) return nullptr; //快指针走到了尽头,表示没有环 fast = fast->next->next; //快指针每次前进两步 slow = slow->next; //慢指针每次前进一步 } while (fast != slow); //相遇则退出,表示有环 // 第二步:找出环的入口节点 fast = head; //快指针重新指向链表开头 while (fast != slow) { 
      //再次相遇时结束查找,相遇点即为环的入口节点 slow = slow->next; //快指针每次前进一步 fast = fast->next; //慢指针每次前进一步 } return fast; } 

5.逻辑推导

         上面的解题思路中,步骤一:判断链表是否有环,这个思路比较简单,很好理解,没有疑问。但是步骤二:通过快慢指针每次都走一步,再次相遇时的节点为环的入口节点,这个查找方法不好理解,并不能直观的确定这种方法是否正确。
        在学习这个算法解题思路的时候,我也是非常好奇,为什么第二次相遇的位置一定是环的入口节点呢?为什么不会是别的节点,会不会永不相遇,程序陷入死循环呢?带着这个疑问,我在草稿上画了一个图形,使用数学方程式的方法来证明算法步骤二的正确性,通过方程式运算,终于解开了心中的疑问。
         下面,通过自己绘制的一张丑陋的图及简单的数学方程式运算步骤,来证明快慢指针第二步找入口点的正确性。



在这里插入图片描述

上图中各部分的说明:
节点1为链表头;
节点2为链表中,环的入口节点;
节点3为链表中,快慢指针首次相遇的节点;
节点4为链表中,下一个节点为入口节点的位置,如果没有环,则这个点为终点;
x:表示从头节点到环的入口节点的步数;
y:表示环的入口节点到快慢指针首次相遇的那个节点的步数;
z:表示从快慢指针第一次相遇的点开始,到环入口节点2的步数。

















 
简单逻辑推导步骤:
1、在查找是否有环的过程中,快指针每次走两步,慢指针每次走一步,那么第一次相遇时,快指针走的步数是慢指针步数的两倍,所以,存在这样一种关系:令n表示相遇前,快指针在环中走完整个环的次数;走完一环需要(y+z)步,数学恒等式如下:(快指针在相遇点开始,绕了n次环,加上x+y即为快指针走的步数)

//慢指针步数的两倍,和快指针走的步数一样多 2*(x+y) = x + y + n*(y+z) 

2、在查找入口节点过程中,快指针重新从链表头开始走,每次前进一步;慢指针从第一次相遇点开始,继续以每次一步的速度向前走。两个指针走的步数是一样的。 如果需要快慢指针相遇,它们一定在节点2处相遇(环的入口节点),否则,在移动速度相同的情况下,它们将永远不会再相遇。我们只需要证明:从首次相遇节点(节点3)开始,慢指针走过x步后一定处于2号节点处即可。(快指针走x步一定是在节点2处的)

//1、如果慢指针从节点3开始走,第一次走到节点2就遇到快指针,则有 x = (y + z) - y;//差y步就走完一圈 //2、如果慢指针从节点3开始走,走过完n-1次完整的环后回到节点3,然后继续往前走(第n圈)到节点2与快指针相遇,则有 x = n*(y + z) - y;//差y步就走完n圈。 

现在只需证明x = n*(y + z) – y就可以证明快慢指针一定在节点2处相遇了。 从步骤一的恒等式可以得到以下推导结果:

//方程式变换 2*(x+y) = x + y + n*(y+z); ==> x = n*(y+z) - y; 

我们用方程式推导出了x = n*(y+z) – y,就证明了快慢节点一定在节点2处相遇,节点2表示环的入口节点,这样我们最终证明了快慢指针第二次相遇的地方即为环的入口点,相遇后,快慢指针将永远指向相同的节点。
我们可以从恒等式 x = n*(y+z) – y和图结合在一起,很容易看出以上的结论是正确的:
当n=1:
x = z;从图上看,慢指针走z步到了节点2,快指针走了x步也刚好到了节点2;
以此类推,m是任意正整数
n = m:
x = m*(y+z) – y。//假设n等于任意正整数m时等式成立
n = m + 1:
x = m*(y+z) – y + (y+z) ==>x = m*(y+z) + z ==>x=(m+1)(y+z)-y //m+1时等式也成立(慢指针再多走一圈时,快慢指针在节点2相遇)
 



















归纳总结:快指针走x步,慢指针走n*(y+z) – y步,快慢移动速度相同时一定存在:x=n*(y+z) – y;对于快指针,走x步刚好到达节点2,对于慢指针,走过n*(y+z) – y步后,也恰好到达节点2。结论:快慢指针相遇点为环的入口节点。


总结

在看快慢指针之前,我的解题思路是容器存着已遍历的节点指针,边遍历,边查找容器,如过下一个节点在容器内,则这个节点即为环的入口节点。这样的解题思路是以空间换时间,对于节点数非常巨大的链表,这种方法显然非常笨拙。使用快慢指针,我们对空间的消耗非常少,查找的代码也非常简单明了,缺点是不太好理解。
本文关键词:
1、快慢指针的概念;
2、判断单链表是否有环;
3、查找有环单链表的环的入口位置;
4、数学归纳法找单链表的环的入口节点。











版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/221243.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月17日 下午6:35
下一篇 2026年3月17日 下午6:35


相关推荐

  • hive正则匹配特殊字符(正则表达式字符串匹配)

    首先,中文字符集为’^[\\4e00-\\u9fa5]$’1.如果直接在Hive命令行中使用,则直接使用‘^[\\u4e00-\\u9fa5]$’进行匹配2.如果在终端调用,则需叫上转义符,如hive-e”select’中国’rlike‘^[\\\u4e00-\\\u9fa5]$’”3.在scala和java中使用同1;valre…

    2022年4月11日
    68
  • 在PyCharm中穿件Vue项目

    在PyCharm中穿件Vue项目0 在 PyCharm 中穿件 Vue 项目 需要先安装 Vue js 的插件完成安装之后重启 PyCharm 1 在 Python 中新建 Vue js 项目 2 创建的项目时提示不要使用大写的字母作为项目名 3 等待下载解释器

    2026年3月16日
    1
  • C++ stringstream 类的用法「建议收藏」

    C++ stringstream 类的用法「建议收藏」(转自:https://blog.csdn.net/nwpu_yike/article/details/22100615)一、类型转换——数字->字符串C++stringstream类是一种十分有用的类,特别是当我们需要在程序中使用字符串和数字数据互相转换的时候。要想在程序中使用stringstream类,我们需要在源程序文件中包含头文件include<sstream&…

    2022年5月1日
    47
  • vue $listeners $attr_vue query

    vue $listeners $attr_vue query1、vm.$attrs2.4.0新增类型{[key:string]:string}只读详细包含了父作用域中不作为prop被识别(且获取)的特性绑定(class和style除外)。当一个组件没有声明任何prop时,这里会包含所有父作用域的绑定(class和style除外),并且可以通过v-bind=”$attrs”传入内部组件——在创建高级别的组件时非常有用。简单点讲就是包含了所有父组件在子组件上设置的属性(除了prop传递的属性、class和styl

    2022年10月10日
    5
  • getParameterValues和getParameter的区别

    getParameterValues和getParameter的区别request.getParameterValues(Stringname)是获得如checkbox类(名字相同,但值有多个)的数据。接收数组变量,如checkobx类型request.getParameter(Stringname)是获得相应名的数据,如果有重复的名,则返回第一个的值.接收一般变量,如text类型例:request.getParameterValues(

    2022年7月22日
    11
  • SRS低延时配置分析

    SRS低延时配置分析SRS 低延时配置分析

    2026年3月19日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号