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

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

相关推荐
hsling松子4 小时前
使用PaddleHub智能生成,献上浓情国庆福
人工智能·算法·机器学习·语言模型·paddlepaddle
dengqingrui1234 小时前
【树形DP】AT_dp_p Independent Set 题解
c++·学习·算法·深度优先·图论·dp
C++忠实粉丝4 小时前
前缀和(8)_矩阵区域和
数据结构·c++·线性代数·算法·矩阵
ZZZ_O^O5 小时前
二分查找算法——寻找旋转排序数组中的最小值&点名
数据结构·c++·学习·算法·二叉树
CV-King5 小时前
opencv实战项目(三十):使用傅里叶变换进行图像边缘检测
人工智能·opencv·算法·计算机视觉
代码雕刻家6 小时前
数据结构-3.9.栈在递归中的应用
c语言·数据结构·算法
雨中rain6 小时前
算法 | 位运算(哈希思想)
算法
Kalika0-07 小时前
猴子吃桃-C语言
c语言·开发语言·数据结构·算法
sp_fyf_20248 小时前
计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-02
人工智能·神经网络·算法·计算机视觉·语言模型·自然语言处理·数据挖掘
我是哈哈hh9 小时前
专题十_穷举vs暴搜vs深搜vs回溯vs剪枝_二叉树的深度优先搜索_算法专题详细总结
服务器·数据结构·c++·算法·机器学习·深度优先·剪枝