[C language brush questions] Fast and slow pointers cleverly solve the problem of single-linked lists with rings

Event address: CSDN 21-day learning challenge

Fast and Slow Pointers Cleverly Solve the Problem of Singly Linked List with Rings

Leetcode141 - Circular Linked List

topic description

Give you the head node head of a linked list, and judge whether there is a ring in the linked list.
If there is a node in the linked list that can be reached again by continuously tracking the next pointer, then there is a cycle in the linked list. In order to represent the ring in the given linked list, the evaluation system internally uses the integer pos to indicate the position where the end of the linked list is connected to the linked list (the index starts from 0). Note: pos is not passed as a parameter. Just to identify the actual situation of the linked list.
Returns true if there is a cycle in the linked list. Otherwise, returns false.

Link: Leetcode141

Example 1:

**Input: **head = [3,2,0,-4], pos = 1 **Output: **true
**Explanation: ** There is a ring in the linked list, its tail is connected to the second nodes.

Example 2:

**Input: **head = [1,2], pos = 0 **Output: **true
**Explanation: **There is a ring in the linked list whose tail is connected to the first node.

Example 3:

**Input: **head = [1], pos = -1 **Output: **false
**Explanation: ** There is no ring in the linked list.

hint:

  • The range of the number of nodes in the linked list is [0, 104]
  • -105 <= Node.val <= 105
  • pos is -1 or a valid .

**Advanced:** Can you solve this problem with O(1) (ie, constant) memory?

core code pattern

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
bool hasCycle(struct ListNode *head) {
    
    
    
}

Idea analysis and implementation code (C language)

Do not traverse the linked list directly. You don't know when you will enter the ring, let alone the length of the linked list.
Here we use the fast and slow pointer to solve. The general idea is as follows:

We define the fast pointer fast and the slow pointer slow, let the fast pointer go first, two steps at a time, and the slow pointer go later, one step at a time (note that the "step" here is a visual statement in units of nodes). We assume that there is a linked list, the first part of which is linear, and the latter part is circular.
When the slow pointer reaches the midpoint of the linear part, the fast pointer has already reached the intersection of the linear part and the circular part.
image.png
Let the two pointers continue to walk. When the slow pointer just enters the ring, the fast pointer has already traveled a certain distance in the ring structure. It is not sure where to go, whether it has walked less than one circle or has already walked n circles , these are uncertain, we first arrange a random position for it here.
image.png
Well, if you go on, what do you think will happen? Intuition tells you that the two of them will eventually meet, right? Why?
First of all, there is a difference in speed between the fast pointer and the slow pointer. The relative speed is 1 step/time. If it is in a completely linear linked list, it is impossible for slow to catch up with fast (this is common sense in high school physics), while in a circular loop structure In the above, it seems that slow who was chasing fast is now being chased by fast. Chasing is relative. Since fast is faster, we believe that fast is chasing slow in the ring structure, so it is only a matter of time before fast catches up with slow.
With this design, will fast be able to catch up with slow in the ring structure? If so, how to prove it briefly? (The following proof is extended to the general purpose)
Proof:
As shown in the figure, let the distance between the two pointers be N when slow first enters the ring (here, the clockwise direction is the positive direction).
image.png
The speed difference between fast and slow is 1 step/time, and fast is faster, so as time goes by, the distance difference between the two pointers is constantly shrinking, does the shrinking mean that they will definitely meet? uncertain. Let's look at the changes in the distance difference between the two pointers: N, N-1, N-2...2, 1, 0, and find that when fast takes two steps at a time and slow takes one step at a time, the distance difference between them is finally It will shrink to 0, that is, the two pointers meet. In fact, whether their distance difference will change to 0 has a lot to do with the speed difference.
No matter how many steps the fast pointer takes at a time or how many steps the slow pointer takes at a time, a simple formula can be used to judge whether they will definitely meet.
Suppose the distance difference at each moment is x, the speed difference between the two pointers is v, and the elapsed time is t (the time mentioned here is not the actual time, but the number of code operations), and the initial distance difference N set before is used, then There is
x = N - vt
to make x become 0, then N - vt needs to be 0, and t is a natural number that is increased by the situation, so N = vt, that is, v = N / t, and v is an integer, So N is required to be a multiple of v.
It can be seen from this that as long as the speed difference v of the two pointers is a divisor of the initial distance difference N, the distance difference x of the two pointers will eventually become 0. We must meet each other.
All positive integers are multiples of 1, so v = 1 will definitely make x finally 0, which means that the fast pointer moves two steps at a time, and the slow pointer moves one step at a time, and they will meet at the end.
image.png

Conclusion:
Through the above description, we found that as long as there is a ring structure in the linked list, then fast will definitely meet slow, then conversely, if fast and slow can meet, then there must be a ring structure (because of the linear case impossible to meet).

Code

bool hasCycle(struct ListNode *head) 
{
    
    
    struct ListNode* fast = head;
    struct ListNode* slow = head;

    while(fast && fast->next)//第一个是对付链表为空的情况,第二个是对付链表没有环状结构的情况
    {
    
    
        fast = fast->next->next;
        slow = slow->next;
        if(fast == slow)
            return true;
    }

    return false;
}

Leetcode142 - Circular Linked List Ⅱ

topic description

Given the head node head of a linked list, return the first node of the linked list starting to enter the ring . Returns null if the linked list is acyclic.
If there is a node in the linked list that can be reached again by continuously tracking the next pointer, then there is a cycle in the linked list. In order to represent the ring in the given linked list, the evaluation system internally uses the integer pos to indicate the position where the end of the linked list is connected to the linked list (the index starts from 0). If pos is -1, there are no cycles in the list. Note: pos is not passed as a parameter, it is just to identify the actual situation of the linked list.
The linked list is not allowed to be modified.

Link : Leetcode142

Example 1:
image.png
**Input: **head = [3,2,0,-4], pos = 1 **Output: **Return the linked list node with index 1**
Explanation: **There is a ring in the linked list, Its tail connects to the second node.

Example 2:

**Input: **head = [1,2], pos = 0 **Output: **Return the linked list node whose index is 0**
Explanation: ** There is a ring in the linked list, its tail is connected to the first a node.

Example 3:

**Input: **head = [1], pos = -1 **Output: **return null
**Explanation: ** There is no ring in the linked list.

hint:

  • The number of nodes in the linked list ranges in the range [0, 104]
  • -105 <= Node.val <= 105
  • The value of pos is -1 or a valid index in the linked list

Advanced : Can you solve this problem using O(1) space? core code pattern


/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode *detectCycle(struct ListNode *head) {
    
    
    
}

Idea analysis and code implementation (C language)

1. Formula method

This method mainly relies on a derived formula. If you can think of it, the code is very simple, but it is really hard to think of it. Still use the speed pointer, refer to the previous question.

Derivation process :
As shown in the figure, let the length of the linear part of the linked list be L, the distance between the entry point of the ring and the meeting point of the fast and slow pointer be X, and the length of the ring be C.
image.png
Then the distance traveled by slow is L+X, and since the distance traveled by fast is twice that, fast travels 2(L+X). In fact, fast may have already traveled the distance of n circles plus CX in the ring before the slow pointer reaches the entrance
of the ring.
image.png
Fast has gone 2X, so the total distance traveled by fast can also be expressed as L+nC+(CX)+2X, that is, L+nC+C+X.
So there is the following equation:
2(L+X) = L+nC+C+X
L+X = nC+C
L =nC+CX
Okay, so what is the use of this obtained equation?

Conclusion:
One pointer starts from the head of the linked list, and the other pointer starts from the meeting point of the double rate (meaning that the speed difference is 1) fast and slow pointers. The linked list begins to enter the first node of the ring.

Code

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

    while(fast && fast->next)
    {
    
    
        fast = fast->next->next;
        slow = slow->next;
        if(fast == slow)
        {
    
    
            meet = slow;
            while(head != meet)
            {
    
    
                head = head->next;
                meet = meet->next;
            }
        }
    }

    return NULL;
}

2. Conversion intersection method

The above idea is really hard to think of, here is a better idea, although the code is more cumbersome.
You see, the linear part of the linked list with a ring and the circular part intersect, and there must be an intersection point, right? Then can you "cut" the ring, cut it in two, and directly become a "Y"-shaped intersecting linked list, the original The entrance to the ring becomes the first intersection point of the intersecting linked list, so the problem is transformed into the problem of finding the first intersection point of the intersecting linked list. But, can you find a random place to cut the ring? This is definitely not possible, you don’t know whether you have entered the ring or not, that is to say, the location of the ring is not easy to determine, so what’s the matter? Use the speed pointer to find the meeting point, and use the meeting point as the boundary.
The intersecting linked list has the original title, link: Leetcode160 , so I won’t go into details here.
It should be noted that since the title says that the pointer cannot be modified, it is best to splice the ring back after we cut off the ring and find the entrance to the ring.
image.png

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
    
    
     if(headA == NULL && headB == NULL)
         return NULL;

    struct ListNode* tailA = headA;
    struct ListNode* tailB = headB;

    int cntA = 1;
    int cntB = 1;

    while(tailA->next)
    {
    
    
        tailA = tailA->next;
        cntA++;
    }

    while(tailB->next)
    {
    
    
        tailB = tailB->next;
        cntB++;
    }

    if(tailA != tailB)
        return NULL;

    struct ListNode* longList = headA;
    struct ListNode* shortList = headB;

    if(cntA < cntB)
    {
    
    
        longList = headB;
        shortList = headA;
    }

    int gap = abs(cntA - cntB);

    while(gap--)
    {
    
    
        longList = longList->next;
    }

    while(longList != shortList)
    {
    
    
        longList = longList->next;
        shortList = shortList->next;
    }

    return longList;

}

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

    while(fast && fast->next)
    {
    
    
        fast = fast->next->next;
        slow = slow->next;
        if(fast == slow)
        {
    
    
            meet = slow;
            struct ListNode* newHead = meet->next;
            meet->next = NULL;
            struct ListNode* entryNode = getIntersectionNode(head, newHead);
            meet->next = newHead;
            return entryNode;
        }
    }
    return NULL;
    
}

insert image description here

Guess you like

Origin blog.csdn.net/weixin_61561736/article/details/126282812
Recommended