[杂记]算法: 快慢指针


打算以后记录一些比较有代表性的算法. 仅从初学者角度对算法进行简单解读, 以力扣题为例.


0. 快慢指针

快慢指针是双指针的一种. 一般来说, 在判断存不存在环结构的时候比较有用. 通常来讲, 我们通过指针进行遍历, 快指针一次走两步, 慢指针一次走一步, 快慢指针相遇往往就意味着某些我们想要的信息. 还是以例题为素材.

1. 环形链表

题目链接: 环形链表II

题目要求我们找到链表入环的第一个节点. 如果判断链表是否存在环, 则只需要让快指针一次走两步, 慢指针一次走一步, 判断二者是否相遇即可. 但是要找到第一个节点, 我们需要先写出公式进行推导.

在这里插入图片描述

如图, 我们假设环外的直线部分的长度为 a a a(在这里为3), 在相遇点之前环中的长度为 b b b, 相遇点之后环中的长度为 c c c, 当然 b + c = 环长 b+c=环长 b+c=环长.

假设在相遇的时候, 快指针已经在环内走了 n , n ≥ 1 n, n\ge 1 n,n1圈, 那么慢指针走过的长度一定是 a + b a+b a+b. 快指针走的长度是 a + b + n ( b + c ) a + b + n(b + c) a+b+n(b+c)

慢指针走过的长度不可能是 a + k b , k ≥ 2 a+kb, k\ge 2 a+kb,k2, 因为如果慢指针走过完整的环, 则其必与快指针相遇. 这是因为考虑二者的相对速度, 在慢指针看来, 快指针的速度为1个单位, 所以快指针必在一个环内追上慢指针.

我们利用快指针走过的长度一定是慢指针的2倍这个事实, 得到
a + b + n ( b + c ) = 2 ( a + b ) a + b + n(b + c)=2(a+b) a+b+n(b+c)=2(a+b)

我们要求的是进入环的第一个节点, 也就是a的值, 化简上式得到:

a = c + ( n − 1 ) ( b + c ) a=c+(n-1)(b+c) a=c+(n1)(b+c)

因此, a一定是环长的倍数加c, 这意味着, 如果我们将慢指针从相遇点再往下遍历, 同时找一个辅助指针auxPtr从头开始, 则慢指针与auxPtr相遇的时候, 慢指针绕过了 n − 1 n-1 n1圈再多 c c c, 而auxPtr走到 a a a, 正是环的入口处.

在这里插入图片描述

当fast可能到达链表末尾时, 说明不存在环, 返回null. 因此编写程序如下:

class Solution {
    
    
public:
    ListNode *detectCycle(ListNode *head) {
    
    
        ListNode* fast = head, *slow = head;  // 定义快慢指针
        ListNode* auxPtr = head;  // 辅助指针

        while(fast) {
    
    
            slow = slow->next;
            if (fast->next) fast = fast->next->next; 
            else break;

            if (fast == slow) {
    
      // 若相遇 表明有环 辅助指针与慢指针同时走
                while (auxPtr != slow) {
    
    
                    auxPtr = auxPtr->next;
                    slow = slow->next;
                }
                return auxPtr;
            }
        }

        return nullptr;
    }
};

2. 寻找重复的数

题目链接: 寻找重复数

有一个长度为 n + 1 n+1 n+1的数组, 每个元素的范围都在 [ 1 , n ] [1,n] [1,n]内, 根据鸽巢原理, 必存在一个重复的数. 找到这个重复的数.

这道题也是可以通过快慢指针法求解的. 我们还是将数组想象成一个链表, 然而并不是按照数组顺序排列的链表. 假设一个数组, 其索引2和4的位置的值都为3, 如下所示:

在这里插入图片描述
那么我们考虑 i d x → n u m s [ i d x ] idx\rightarrow nums[idx] idxnums[idx]的映射, 当 i d x = 2 idx=2 idx=2时, 我们得到 3 3 3, 我们再考虑 n u m s [ 3 ] nums[3] nums[3], 它的值可以是随便一个数, 但是一定不会超过数组的长度 n n n. 因此如此下去, 这个 n u m s [ i d x ] nums[idx] nums[idx]的值必然会回到2或4(因为假设数组中没有4, 则还可以再回到2), 因此, 回到2或4之后再次进行映射, 就又回到了重复值3. 因此上题中 → n e x t \rightarrow next next的操作在本题中相当于进行 n u m s [ i d x ] nums[idx] nums[idx]的映射, 从3再回到3的过程, 就是在环中遍历的过程, 这个过程当然可长可短.

因此编写代码如下:

class Solution {
    
    
public:
    int findDuplicate(vector<int>& nums) {
    
    
        int slow = 0, fast = 0;

        do {
    
    
            slow = nums[slow]; // slow = slow->next
            fast = nums[nums[fast]];  // fast = fast->next->next
        } while(slow != fast);

        int auxPtr = 0;
        while(slow != auxPtr) {
    
    
            auxPtr = nums[auxPtr];
            slow = nums[slow];
        }
        return auxPtr;
    }
};

猜你喜欢

转载自blog.csdn.net/wjpwjpwjp0831/article/details/127757374