快慢指针

快慢指针可用于判断链表中是否有环。

Floyd判圈算法(龟兔赛跑算法)

假设乌龟和兔子在链表上跑步,兔子跑得快,乌龟跑的慢,如果链表中没有环,那么兔子将会一直在乌龟前面,直到终点;反之,如果链表中存在环,那么兔子一定会和乌龟再次相遇。

我们可以设定两个指针,快指针fast指代兔子,慢指针slow指代乌龟,假设快指针一次走两步,慢指针一次走一步。

初始化时有两种方法:

1.fast = head.next , slow = head;用fast != slow当作循环条件

2.fast = slow = head,使用do-while()循环。

例题:

1.环形链表(https://leetcode-cn.com/problems/linked-list-cycle/)

给定一个链表,判断链表中是否有环。

思路:快慢指针模板题

class Solution {
public:
    bool hasCycle(ListNode *head) {
        if(head == nullptr || head->next == nullptr) return false;
        ListNode* fast;
        fast = head->next;
        ListNode* slow = head;
        while(slow != fast){
            if(fast == nullptr || fast->next == nullptr) return false;
            slow = slow->next;
            fast = fast->next->next;
        }
        return true;
    }
};

2.环形链表 II (https://leetcode-cn.com/problems/linked-list-cycle-ii/)

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

思路:跟上一题相比多了一个地方,需要找到入环点。我们设以下几个变量:

a:表示头节点到入环点之间的距离

b:慢指针在圈中所走的距离

c:整圈距离减去b所走距离的剩余距离

由此可以得到快慢指针相遇时,快指针所走距离:a+n*(a+c)+b(按第一种方法,快指针所走距离需要减1,因为fast=head->next),慢指针所走距离:a+b

而快指针的速度是慢指针的两倍,故快指针所走距离也是慢指针所走距离的两倍

a+n(b+c)+b = 2(a+b) => a = (n-1)b + nc

由此我们可以得出,当慢指针从相遇得地方再走c个距离,就会走到入环点,我们另建一个指针ptr=head,当ptr == slow时,即为入环点

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        if(head == nullptr || head->next == nullptr) return nullptr;
        ListNode * fast = head->next,*slow = head;
        while(slow != fast){
            if(fast == nullptr || fast->next == nullptr) return nullptr;
            slow = slow->next;
            fast = fast->next->next;
        }
        // cout<<"********\n";
        ListNode* ptr = head;
        slow=slow->next; //使用方法一需要改的地方
        while(ptr != slow){
            ptr = ptr->next;
            slow = slow->next;
        }
        return ptr;
    }
};

3.快乐数(https://leetcode-cn.com/problems/happy-number/)

编写一个算法来判断一个数 n 是不是快乐数。

思路:通过模拟可以发现不管几位数最后都会小于243,然后结果不是归为1,就是会陷入一个循环当中。因此,我们可以使用快慢指针判断是否有环的存在

class Solution {
public:
    int getNum(int n){
        int sum = 0;
        while(n>0){
            int x = n%10;
            n /= 10;
            sum += (x*x);
        }
        return sum;
    }
    bool isHappy(int n) {
        if(n==1) return true;
        int slow = n;
        int fast = getNum(n);
        // cout<<fast<<endl;
        while(fast!=1 && slow != fast){
            slow = getNum(slow);
            fast = getNum(getNum(fast));
        }
        return fast==1;
    }
};

4.链表的中间结点(https://leetcode-cn.com/problems/middle-of-the-linked-list/)

给定一个头结点为 head 的非空单链表,返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点。

思路:这是快慢指针最简单的应用场景了,当快指针到达终点时,慢指针刚好到达中点(终点中点不一样)

class Solution {
public:
    ListNode* middleNode(ListNode* head) {
        ListNode* fast = head;
        ListNode* slow = head;
        if(head->next == nullptr) return head;
        do{
            slow = slow->next;
            fast = fast->next->next;
            // cout<<slow->val<<' '<<fast->val<<endl;
        }while(fast != nullptr && fast->next != nullptr);
        // cout<<fast->val<<' '<<fast->next->val<<endl;
        return slow;
    }
};

posted @ 2021-02-02 13:09  voids5  阅读(126)  评论(0编辑  收藏  举报