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

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

相关推荐
懒惰才能让科技进步24 分钟前
从零学习大模型(十二)-----基于梯度的重要性剪枝(Gradient-based Pruning)
人工智能·深度学习·学习·算法·chatgpt·transformer·剪枝
Ni-Guvara38 分钟前
函数对象笔记
c++·算法
泉崎1 小时前
11.7比赛总结
数据结构·算法
你好helloworld1 小时前
滑动窗口最大值
数据结构·算法·leetcode
AI街潜水的八角2 小时前
基于C++的决策树C4.5机器学习算法(不调包)
c++·算法·决策树·机器学习
白榆maple2 小时前
(蓝桥杯C/C++)——基础算法(下)
算法
JSU_曾是此间年少2 小时前
数据结构——线性表与链表
数据结构·c++·算法
此生只爱蛋3 小时前
【手撕排序2】快速排序
c语言·c++·算法·排序算法
咕咕吖4 小时前
对称二叉树(力扣101)
算法·leetcode·职场和发展
九圣残炎4 小时前
【从零开始的LeetCode-算法】1456. 定长子串中元音的最大数目
java·算法·leetcode