【算法杂谈】1.双指针之快慢指针

虽然双指针和快慢指针的真正联系只是"Two Pointers"而已,但两种算法的思想还是不太相同的,所以就用本篇博客单独提出来说明。

快慢指针原称Floyd's cycle-finding algorithm,该算法会使用两个在数组(序列/链表)种以不同速度移动的指针。因此也被称为Floyd龟兔算法

说起龟兔,自然而然的就想到龟兔赛跑的故事,而该算法的核心思想就是与这个故事有关。我们可以把两个指针视为龟指针和兔指针,让龟兔以不同的速度向前步进,以方便获取目标。

由上图简单分析可知,乌龟一次爬一步,兔子一次跑两步。直到兔子到达了终点。这样,二者的距离每次移动后加1

那么,我们不妨假设链表有n个元素,那么最多移动n/2轮。

当n为奇数时,乌龟就刚好停在中间节点,

当n为偶数时,乌龟刚好停在中间两个节点靠前的一个。 由此,龟兔算法用于处理链表/数组中的循环问题,找寻链表的中点或者需要知道特定元素的位置。以及处理回文的问题

说这么多可能还是蒙蔽,写算法关键还得是实战。那么以下请看例题。

实战1:判断是否为环形链表(LeetCode 141)

解题思路:按照龟兔算法思想,首先将龟兔处于同一起跑线,也就是首结点 3 的位置,在给予龟兔不同的初速度(一般为兔子 = 2 * 乌龟),那么当兔子先一步跑入 2 - 0 - 4 的环形链表中,它就会一直在这里循环跑圈,而当乌龟也慢悠悠的爬进环形链表中,二者的相对速度则是 1,这样在n次循环后,二者就可以在循环链表中相遇。

Java 复制代码
public boolean hasCycle(ListNode head) {
      if (head == null || head.next == null) return false;
      // 使用快慢指针 (适用于循环问题,或者中点位置)
      // 标记慢指针
      ListNode slow = head;
      // 标记快指针
      ListNode fast = head;
      // 当二者不相等时,则进行下一步
      while (slow != fast) {
         // 当快指针本身位空,或者快指针的下一个指向为空,则说明为单链表
         if (fast == null || fast.next == null) {
             return false;
         }
         // 更新快指针
         slow = slow.next;
         // 更新慢指针
         fast = fast.next.next;
     }
     return true;
 }

番外:这一题还有一种思路,就是通过改变经过节点的值(一个特殊的值),当循环到一样值的时候,则意味着有循环链表了。

此时就有人要问了,这只能判断有没有循环呀,那我要求循环链表开始的位置,该怎么做呢?咦,不要急,就等你这一句话呢!下面请看实战2。

实战2:求环链表的第一个结点(LeetCode2)

解题思路:我们先假设 x点是起点(即3),y点为环形链表的起始节点(即2),z点为龟兔相遇的点(未知)。

当龟兔相遇时,此时乌龟刚爬了 (a + b)m,而兔子已经跑了 a + b + n(b + c)m (n表示兔子绕圈次数),而由于兔子的速度是乌龟的两倍,则可以形成等式: 2(a + b) = (a + b)+ n(b + c)。

等式缩减后即可得 a + b = n(b + c);

分析了二者等式之后,我们再来看本题,要求求 y 的位置。

我们就有了两个思路,要么是从 x -> y (a)距离 ,要么是从 z -> y (b 距离)。 第一种思路就可知 a = n(b + c)- b = (n - 1)(b + c) + c ,这样就可以很清晰的得知,当二者相遇时,我们将乌龟重新挪回起点,当它再次爬到 Y点时,这时 兔子刚好也跑到了 Y点。 这种思路可行。 第二种思路就可知 b = n (b + c) - a,此时我们会发现未知数更多了,更加迷糊了,所以这个思路就放弃。那么题解如下:

java 复制代码
public ListNode detectCycle(ListNode head) {
    if (head == null || head.next == null) return null;
    // 标记快指针
    ListNode fast = head;
    // 标记慢指针
    ListNode slow = head;

    // 使用 do-while,如果使用while则第一次就会跳出
    do {
        if(fast == null || fast.next == null) {
            return null;
        }
        fast = fast.next.next;
        slow = slow.next;
    } while(fast != slow);
    // 重置指针,这样就相同了
    slow = head;
    while(fast != slow) {
        fast = fast.next;
        slow = slow.next;
    }
    return slow;
}

通过以上两个例题,大致就明白了快慢指针在环链表中的使用。此时也可以散场了,今天又愉快的学到了一个小知识点。

相关推荐
liulilittle29 分钟前
IP校验和算法:从网络协议到SIMD深度优化
网络·c++·网络协议·tcp/ip·算法·ip·通信
bkspiderx2 小时前
C++经典的数据结构与算法之经典算法思想:贪心算法(Greedy)
数据结构·c++·算法·贪心算法
中华小当家呐3 小时前
算法之常见八大排序
数据结构·算法·排序算法
沐怡旸4 小时前
【算法--链表】114.二叉树展开为链表--通俗讲解
算法·面试
一只懒洋洋4 小时前
K-meas 聚类、KNN算法、决策树、随机森林
算法·决策树·聚类
方案开发PCBA抄板芯片解密5 小时前
什么是算法:高效解决问题的逻辑框架
算法
songx_996 小时前
leetcode9(跳跃游戏)
数据结构·算法·游戏
小白狮ww6 小时前
RStudio 教程:以抑郁量表测评数据分析为例
人工智能·算法·机器学习
AAA修煤气灶刘哥6 小时前
接口又被冲崩了?Sentinel 这 4 种限流算法,帮你守住后端『流量安全阀』
后端·算法·spring cloud
kk”7 小时前
C语言快速排序
数据结构·算法·排序算法