算法---快慢指针最全例题详解

目录

何为快慢指针

eg1.判断单链表是否成环  leetcode.141

eg2.在eg1的基础上寻找环的入口点 剑指 Offer II 022

eg3.寻找链表中的倒数第k个节点 剑指 Offer 22

eg4.删除链表的中间节点 leetcode.2095

eg5.判断链表是否相交并返回相交节点 leetcode.160

        eg.5拾遗


何为快慢指针

        快慢指针为双指针算法的一个具体范例。其核心为声明两个指针,在指针遍历的过程治中风快慢指针所走的步长是不一样的,一般来说慢指针步长常设为1,快指针步长常设为2。

eg1.判断单链表是否成环  leetcode.141

环状链表

        设置慢指针slow和快指针fast,slow的步长为1,fast的步长为2,同时开始遍历链表。

        若链表成环这两个指针则必然会相遇,此时return true

        反之fast则会遍历到null,此时return false

bool hasCycle(struct ListNode *head) 
{
    struct ListNode *fast;
    struct ListNode *slow;
    fast = head;
    slow = head;
    if(head == NULL) { return false; }
    while(1)
    {
        if(fast->next == NULL || fast->next->next == NULL)
        {
            return false;
        }
        fast = fast->next->next;
        slow = slow->next;
        if(fast == slow)
        {
            return true;
        }
    }
}

eg2.在eg1的基础上寻找环的入口点 剑指 Offer II 022

        先判断单链表是否成环,若不成环则无需进行下面操作。

        当快慢指针相遇时,不再有快慢指针的区分,将其中一个指针置于链表表头,另一个指针位置不变,两个继续遍历,当这两个指针再次相遇时,相遇点即为环的入口点。

        其中具体数学过程参见https://zhuanlan.zhihu.com/p/361049436

struct ListNode *detectCycle(struct ListNode *head) 
{
    struct ListNode *fast;
    struct ListNode *slow;
    fast = head;
    slow = head;
    
    //判断链表中是否有环并且找到快慢指针相遇点
    //代码与eg.1相同
    if(head == NULL) { return NULL; }
    while(1)
    {
        if(fast->next == NULL || fast->next->next == NULL)
        {
            return NULL;
        }
        fast = fast->next->next;
        slow = slow->next;
        if(fast == slow)
        {
            break;
        }
    }

    //寻找环的入口点
    fast = head;
    while(1)
    {
        if(fast == slow)
        {
            return fast;
        }
        fast = fast->next;//此时不再设快慢指针,两个指针的步长均为1
        slow = slow->next;
    }
}

eg3.寻找链表中的倒数第k个节点 剑指 Offer 22

        设置慢指针slow和快指针fast,fast比slow快k = 1步,同时开始遍历链表。

        当fast遍历到链表尾时slow正好遍历到链表中的倒数第k个节点。

struct ListNode* getKthFromEnd(struct ListNode* head, int k)
{
    struct ListNode *fast;
    struct ListNode *slow;
    fast = head;
    slow = head;

    //使fast比slow快k - 1步
    for(int i = 1; i < k; ++i)
    {
        fast = fast->next;
    }

    while(fast->next != NULL)
    {
        fast = fast->next;
        slow = slow->next;
    }

    return slow;
}

eg4.删除链表的中间节点 leetcode.2095

        设置慢指针slow和快指针fast,slow的步长为1,fast的步长为2,同时开始遍历链表。

        需按照链表长度分奇偶进行讨论:

        若为偶数:当fast遍历到链表尾时slow正好遍历到链表的中点。

        若为若为奇数:当fast遍历到链表尾时slow正好遍历到链表的中点前的第一个节点。

struct ListNode* deleteMiddle(struct ListNode* head)
{
    struct ListNode *fast;
    struct ListNode *slow;
    struct ListNode *slowformer;
    fast = head;
    slow = head;
    slowformer = NULL;

    if(head->next == NULL || head == NULL) { return NULL; }
    while(1)
    {
        if(fast->next == NULL)//偶数情况
        {
            slowformer->next = slowformer->next->next;
            return head;
        }
        if(fast->next->next == NULL)//奇数情况
        {
            slow->next = slow->next->next;
            return head;
        }
        fast = fast->next->next;
        slowformer = slow;
        slow = slow->next;
    }
}

eg5.判断链表是否相交并返回相交节点 leetcode.160

相交链表

        设置遍历链表A的指针和遍历链表B的指针,将这两个指针分别遍历至链表尾,若此时两个指针指向的内存相同则这两个链表相交。

        找出两个链表长度的差值,并判断哪个链表的长度大。下文中根据长度的不同将这两个链表称为长链表和短链表。

        将这两个指针重新初始化至链表头。

        将遍历长链表的指针向后遍历两个链表长度的差值的步数。遍历短链表的指针位置不变。

        两个指针同时开始遍历,此二指针第一次相遇的节点即为两个单链表的交点。

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) 
{
    struct ListNode *A;
    struct ListNode *B;
    A = headA;
    B = headB;
    int i = 0;
    bool temp;

    while(A->next != NULL && B->next != NULL)
    {
        A = A->next;
        B = B->next;
    }
        
    while(A->next != NULL)
    {
        A = A->next;
        ++i;//i为两个链表长度的差值
        temp = false;//temp表示A的长度大于B
    }

    while(B->next != NULL)
    {
        B = B->next;
        ++i;//i为两个链表长度的差值
        temp = true;//temp表示B的长度大于A
    }

    if(A != B) { return NULL; }//判断是否相交

    A = headA;
    B = headB;

    if(temp)//将两个指针之后的单链表长度相同
    {
        for(int j = 0; j < i; ++j)
        {
            B = B->next;
        }
    }
    else
    {
        for(int j = 0; j < i; ++j)
        {
            A = A->next;
        }        
    }

    while(A != B)//若两个指针相遇,则此节点为两个单链表的交点
    {
        A = A->next;
        B = B->next;
    }

    return A;
}

        eg.5拾遗

        此题目中规定两个单链表不成环,下面讨论成环的情况。

        若一个单链表成环,但另一个不成环,则这两个单链表必不相交。

一个单链表成环,但另一个不成环

        若两个单链表均成环,可根据eg2找出环的入口点,判断这两个入口点是否指向同一块内存来判断是否相交。或者可以根据遍历两个链表的指针是否相遇来判断是否相交。

两个单链表均成环
  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LXTTTTTTTT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值