快慢指针 - 关于链表oj题的思路延申

目录

                                    1.概念

2.思考:为什么慢指针走1步,快指针走两步,快指针一定会追上?

3.思考:如果快指针一次走3步,4步,5步...n步行不行?

4.思考:为什么一个指针从头指针的位置开始向后访问,一个指针从快慢指针相遇的地方开始向后访问,两个指针会在入环的第一个结点相遇?

5.例题


1.概念

    定义:快慢指针,即慢指针一次走一步,快指针一次走两步,两个指针从链表其实位置开始运行,如果链表带环则一定会在环中相遇,否则快指针率先走到链表的末尾。

注:以下名词在接下来的书面中均以英文代替

      头指针   head

      快指针   fast

      慢指针   slow

     快慢指针相遇点  meet

2.思考:为什么慢指针走1步,快指针走两步,快指针一定会追上?

如图所示:

       假设slow进环后与fast之间的结点距离为N,这个时候fast才真正开始追击slow,slow每次访问一个结点,fast每次访问两个结点,所以它们之间的距离每次都会缩减1个结点。

       也就是  N   N-1  N-2  N-3 ... 3  2  1  0 当两者之间的结点距离为0时,也就意味着快指针追上慢指针。

3.思考:如果快指针一次走3步,4步,5步...n步行不行?

如图所示:

       设slow进环,环的大小为C,这个时候fast才真正开始追击slow,slow每次访问一个结点,fast每次访问三个结点,所以它们之间的距离每次都会缩减两个结点。

       这时就会分情况讨论:

N是偶数:N   N-2  N-4  N-6 ... 6  4  2  0  此时,fast追上了slow。

N是奇数: N   N-2  N-4  N-6 ... 5  3  1  -1 当两者之间的结点距离为-1时,意味着快fast与slow之间的距离为 C - 1。

此时:偶数情况下,基本追的上,但是奇数情况下,也有一定程度上可能追不上。

综述,3,4...n步皆是视情况而定,不一定能追上,但是两步是一定能追上。

4.思考:为什么一个指针从头指针的位置开始向后访问,一个指针从快慢指针相遇的地方开始向后访问,两个指针会在入环的第一个结点相遇?

如图所示:

      假设 head 到访问入环的第一个结点之间的距离为 L,环的大小为 C ,slow 进环所访问距离为 X,fast所访问距离为N。

那么  L = N*C - X

        slow进环后,fast在一圈之内,必定追上slow。因为在追击过程中,它们之间相隔的距离每次都会缩减1,当slow访问一圈结点后,fast必定已经访问了两圈结点,所以它们之间的相对距离是一圈。

如图所示:

       而 N*C - X 不就是快慢指针相遇的结点嘛。

结论:

       一个指针从头指针的位置开始向后访问,一个指针从快慢指针相遇的地方开始向后访问,两个指针会在入环的第一个结点相遇。

       可能会有些伙伴问这个有什么作用,这不是纯数学推导嘛,跟逻辑有什么关系,不妨我们来看看例题第四个问题。如果我们知道这个原理,可能代码复杂程度会大大降低。

5.例题

1.求链表中的中间结点。oj习题

思路:定义一组快慢指针,fast为2倍的slow。两者同时从head的地址位置开始向后访问,当fast向后访问到NULL时,此时slow的位置即为中间结点。

struct ListNode* middleNode(struct ListNode* head){
    
    struct ListNode* fase,*slow;

    fase = slow = head;

    while(fase && fase->next)//不清楚是奇数还是偶数
    {
        fase = fase->next->next;
        slow = slow->next;
    }
   
       return slow;
}

2.求链表中倒数第K个结点。oj习题

思路:定义一组快慢指针,fast为2倍的slow。此时fast从head的地址位置开始向后先访问k个结点,然后再一起向后访问,当fast访问到NULL,slow就是倒数第k个结点。

struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
    // write code here
    if(pListHead == NULL)
    {
        return NULL;
    }

       struct ListNode* fase,*slow;
       fase = slow = pListHead;

       while(k--)//先让fase先走k步
       {
           if(fase == NULL)//如果fase走到空,说明链表没有k步长
           {
               return NULL;
           }

          fase=fase->next;
       }
        
       while(fase)
       {
           fase = fase->next;
           slow = slow->next;
       }

       return slow;

    }

3.给定一个链表,判断链表中是否有环。oj习题

思路:定义一组快慢指针,fast为2倍的slow。两者同时从head的地址位置开始向后访问,当两者地址相等时,就证明链表中有环。反之当fast访问到NULL,两者的地址都不相等,则无环。

bool hasCycle(struct ListNode *head) {
    if(head == NULL)
    {
        return false;
    }

    struct ListNode *fast, *slow;
    fast = slow = head;

    while(fast && fast->next)//不清楚是否带环,链表是否奇偶
    {
        fast = fast->next->next;
        slow = slow->next;

        if(slow == fast)
             return true;
    }

    return false;
    
}

4.给定一个链表的头节点  head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。oj习题

思路:定义一组快慢指针,fast为2倍的slow。

方法①. 运用4.思考 一个指针从头指针的位置开始向后访问,一个指针从快慢指针相遇的地方开始向后访问,两个指针会在入环的第一个结点相遇。

struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode *fast, *slow;
    fast = slow = head;

    while(fast && fast->next)//不清楚是否带环,链表是否奇偶
    {
        fast = fast->next->next;
        slow = slow->next;

        if(slow == fast)
        {
            struct ListNode* meet = slow;//此时slow == fast == meet
            while(meet != head)
            {
                meet = meet -> next;
                head = head ->next;
            }

            return meet;//此时meet == head 就是入环的第一个结点
        }
             
    }

    return NULL;  
}

方法②.创建一个指针从meet位置的下一个结点开始向后访问,求两个链表相交的交点。(如下图所示)

 

 这里需要考虑一个特殊情况,如果meet == head怎么办?

struct ListNode *detectCycle(struct ListNode *head) 
{
    struct ListNode* fast, * slow;
    fast = slow = head;

    while (fast && fast->next)
    {
        //快慢指针同时向后开始访问
        fast = fast->next->next;
        slow = slow->next;

        //如果它们访问的地址相同,那么这个结点就是快慢指针相遇点
        if (slow == fast)
        {
            struct ListNode* meet = slow;

            if (head == meet)//如果head等于快慢指针相遇结点,就证明头指针就是入环的第一个结点
            {
                return head;
            }

            struct ListNode* curA = head, * curB = meet->next;

            int lenA = 1, lenB = 1; //求长度,从1开始,不然尾结点会漏掉


            while (curA->next != meet)
            {
                curA = curA->next;
                lenA++;
            }

            while (curB->next != meet)
            {
                curB = curB->next;
                lenB++;
            }

            //此时curA,curB都是尾结点

            //求第一个交点

             //假设A短B长
            struct ListNode* shortList = head, * longList = meet->next;
            //修正 如果A长B短,则交换彼此变量
            if (lenA > lenB)
            {
                shortList = meet->next;
                longList = head;
            }

            //差距
            int gap = abs(lenA - lenB);//abs求绝对值

            while (gap--) //长的先走差距步
            {
                longList = longList->next;
            }

            //同时走
            while (shortList != longList)
            {
                shortList = shortList->next;
                longList = longList->next;
            }


            //到这里肯定相等,返回 shortList / longList 都可以。

            return shortList;

        }
    }

    return NULL;//如果fast访问到NULL即证明无环
}

猜你喜欢

转载自blog.csdn.net/m0_73969113/article/details/131545793
今日推荐