ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

算法学习笔记 --《快慢指针及其应用》

2021-05-23 19:00:34  阅读:235  来源: 互联网

标签:快慢 -- 相遇 入口 链表 节点 指针


算法学习笔记 --《快慢指针及其应用》


前言

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

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

一、什么是快慢指针

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

二、快慢指针的应用

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次完整的环后回到节点3,然后继续往前走到节点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;
当n=2:
x = y+z+z;从图上看,慢指针走一圈(y+z),再走z步也是到节点2,快指针走了x步也刚好到了节点2;
以此类推 n = n:
x = (n-1)(y+z) +z;慢指针走完n-1圈,再走z步也最终到达节点2.。

总结

在看快慢指针之前,我的解题思路是容器存着已遍历的节点指针,边遍历,边查找容器,如过下一个节点在容器内,则这个节点即为环的入口节点。这样的解题思路是以空间换时间,对于节点数非常巨大的链表,这种方法显然非常笨拙。使用快慢指针,我们对空间的消耗非常少,查找的代码也非常简单明了,缺点是不太好理解。

标签:快慢,--,相遇,入口,链表,节点,指针
来源: https://blog.csdn.net/woody218/article/details/117114061

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有