【算法杂谈】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;
}

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

相关推荐
带多刺的玫瑰11 分钟前
Leecode刷题C语言之切蛋糕的最小总开销①
java·数据结构·算法
巫师不要去魔法部乱说21 分钟前
PyCharm专项训练5 最短路径算法
python·算法·pycharm
qystca1 小时前
洛谷 P11242 碧树 C语言
数据结构·算法
冠位观测者1 小时前
【Leetcode 热题 100】124. 二叉树中的最大路径和
数据结构·算法·leetcode
悲伤小伞1 小时前
C++_数据结构_详解二叉搜索树
c语言·数据结构·c++·笔记·算法
m0_675988232 小时前
Leetcode3218. 切蛋糕的最小总开销 I
c++·算法·leetcode·职场和发展
佳心饼干-4 小时前
C语言-09内存管理
c语言·算法
dbln4 小时前
贪心算法(三)
算法·贪心算法
songroom5 小时前
Rust: offset祼指针操作
开发语言·算法·rust
chenziang17 小时前
leetcode hot100 环形链表2
算法·leetcode·链表