Singly linked list common algorithm questions (full)



foreword


All questions come from Leetcode, I want to use this blog to record my thinking process.

Introduction to Header Nodes on Leetcode


The singly-linked list on the link question has no header, that is, the zeroth node. The head node in the question refers to the first data node.
Generally, the head node we refer to is the zeroth node, also known as a dummy node or a header .
In order to avoid ambiguity, the zeroth node is collectively referred to as a dummy node in this article , and the head node in the question refers to the first data node .
As for the pointer to the dummy node, I named it dummyHead .



topic


1. Remove linked list elements (203 questions)

Problem description: You are given a linked list head node head and an integer val, please delete all nodes in the linked list that satisfy Node.val == val, and return a new head node.
Example 1:
Input: head = [1,2,6,3,4,5,6], val = 6
Output: [1,2,3,4,5]
Example 2:
Input: head = [], val = 1
output: []
example 3:
input: head = [7,7,7,7], val = 7
output: []

Idea 1: Create a temporary dummy node

In the delete operation, if a dummy node is not created, then the first data node (referred to as the head node) needs to be distinguished from other data nodes.
Creating temporary dummy nodes can be operated uniformly without classification.
After creating dummyHead, 2 pointers are needed, and the cur pointer points to the node being checked.
The prec pointer points to the predecessor node of the checked node, because after deleting the current node, the predecessor node needs to be connected to the successor node of the deleted node.
That is, prec->next = cur->next;
if the value of the currently checked node is equal to val, let prec->next = cur->next, and release the currently deleted node.
If the value of the currently checked node is not equal to val, move the predecessor pointer prec to the next bit.
Regardless of whether the current node needs to be deleted or not, the current pointer cur needs to point to the next node, that is, cur=prec->next;
if cur=NULL, it means that the check is over.
Note: The return value is the next bit of the dummy node, because the original head node may also be deleted.

struct ListNode* removeElements(struct ListNode* head, int val){
    
    
	// 创建临时哑结点
    struct ListNode* dummyHead = (struct ListNode*)malloc(sizeof(struct ListNode));
    dummyHead->next = head;						// 让哑结点连接头结点
    struct ListNode* prec = dummyHead;			// 让prec指针指向当前被检查结点的前驱结点
    struct ListNode* cur = prec->next;			// 让cur指针指向当前被检查结点
    while(cur)
    {
    
    
        if (cur->val == val)					// 如果当前被检查结点的值=val
        {
    
    
            prec->next = cur->next;				// 让其前驱结点连接其后继结点
            free(cur);							// 释放当前被检查结点
        }
        else
        {
    
    
            prec = prec->next;					// 如果当前结点和val不相等,就让prec前驱指针移动到下一位
        }
        cur = prec->next;						// 无论val和当前结点是否相等,都让cur往下移动一位
    }
    return dummyHead->next;						// 返回哑结点指向的下一个结点
}

Complexity analysis:

  • Time complexity : O(n). Need to traverse the current linked list.
  • Space complexity : O(1). A space for a dummy node, two pointer spaces prec and cur are required.

Idea 2: Recursive method

If n(k+1) and subsequent nodes have been removed, only the head node needs to be checked.
If the head node needs to be removed, return the next node of the head node, otherwise return the head node.

struct ListNode* removeElements(struct ListNode* head, int val){
    
    
    if (head == NULL)								// 如果head为空,就返回NULL
        return NULL;
    head->next = removeElements(head->next, val);	// 让头结点连接上已经被移除的链表
    return head->val == val ? head->next : head;	// 如果头结点需要删除,就返回head->next,否则返回头指针head
}

Complexity analysis:

  • Time complexity : O(n). Need to traverse the current linked list.
  • Space complexity : O(n). It needs to recurse n times, each time requires O(1) space complexity, and n times requires O(n).

2. Reverse linked list (206 questions)

Problem description: Give you the head node head of the singly linked list, please reverse the linked list and return the reversed linked list.
Example 1:
Input: head = [1,2,3,4,5]
Output: [5,4,3,2,1]
Example 2:
Input: head = [1,2]
Output: [2,1]
Example 3:
Input: head = []
Output: []

Idea 1: Use the head insertion method

Create a temporary dummy node and initialize its next field to NULL.
Let the cur pointer point to the node that needs to be inserted currently.
Each time the node is inserted behind the dummy node.
Let the cur pointer move back one bit.
After the loop ends, the nodes will be inserted behind the dummy nodes in reverse order, and the inversion work is completed.

struct ListNode* reverseList(struct ListNode* head){
    
    
	// 创建一个哑结点(第零个结点)
    struct ListNode dummyNode = {
    
    .next = NULL};
    // head指向当前未被插入的结点
    while (head)
    {
    
    
        struct ListNode* cur = head;		// cur指针指向当前被插入的结点
        head = head->next;					// 让head指向下一个结点
        cur->next = dummyNode.next;			// 让当前被插入的结点指向哑结点后的下一个结点
        dummyNode.next = cur;				// 让哑结点指向当前被插入的结点
    }
    return dummyNode.next;					// 返回哑结点指向的结点
}

Complexity analysis:

  • Time complexity : O(n). Need to traverse the current linked list.
  • Space complexity : O(1). Space for a dummy node dummyNode and a cur pointer is required.

Idea 2: Directly change the order of node connections

Suppose the linked list is 1->2->3->NULL, just change it to NULL<-1<-2<-3.
Algorithm idea:
Let the next pointer field of the current node point to its predecessor node in sequence, and the reversal is completed.
Three pointers are required: prec points to the predecessor node , cur points to the current node , and succ points to the successor node .
Because the next pointer field of the current node is changed from pointing to the successor node to pointing to the predecessor node.
Doing so will cause the successor node and the nodes after it to be lost.
Therefore, the succ pointer needs to point to the address of the successor node so as not to be lost.

struct ListNode* reverseList(struct ListNode* head){
    
    
    struct ListNode* cur = head;				// 定义指向当前结点的指针
    struct ListNode* prec = NULL;				// 定义指向前驱结点的指针
    while (cur)
    {
    
    
        struct ListNode* succ = cur->next;		// 定义指向后继结点的指针
        cur->next = prec;						// 让当前结点指向其前驱结点
        prec = cur;								// 让prec指针指向下一位
        cur = succ;								// 让cur指针指向下一位
    }
    return prec;								// 循环结束后,prec指向原本的最后一个结点,也就是反转后的第一个结点
}

Complexity analysis:

  • Time complexity : O(n). Need to traverse the current linked list.
  • Space complexity : O(1). Need space for three pointers prec, cur, succ.

Idea 3: Use recursion

The core idea of ​​recursion is:
Assuming that the n+1th node and its subsequent nodes are reversed, only the kth node needs to be reversed.
You need to let nk+1->next = nk;
because nk+1 = nk->next, you need to let nk->next->next = nk;
then you need to let nk->next point to NULL.
That is, let the last node point to NULL.

struct ListNode* reverseList(struct ListNode* head){
    
    
	// 如果是空链表或者只有一个元素的链表,直接返回头结点
    if (head == NULL || head->next == NULL)		
        return head;
    // 假设head结点之后的所有结点都反转了
    struct ListNode* newHead = reverseList(head->next);	
    head->next->next = head;		// 让head结点的下一个结点指向head结点
    head->next = NULL;				// 再让head指向NULL,为了让头结点反转后指向NULL
    return newHead;  				// 返回反转后的第一个结点
}

Complexity analysis:

  • Time complexity : O(n).
  • Space complexity : O(n). Requires n stacks, and the space complexity is O(n).

3. The intermediate node of the linked list (876 questions)

Problem Description: Given a non-empty singly linked list whose head node is head, return the middle node of the linked list. If there are two intermediate nodes, return the second intermediate node.
Example 1:
Input: [1,2,3,4,5]
Output: Node 3 in this list (serialized form: [3,4,5])
returns a node value of 3. (The serialized representation of the node in the evaluation system is [3,4,5]).
Note that we return an object ans of type ListNode such that:
ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, and ans.next.next.next = NULL.
Example 2:
Input: [1,2,3,4,5,6]
Output: Node 4 in this list (serialized form: [4,5,6])
Since this list has two intermediate nodes, the value 3 and 4 respectively, we return the second node.

Idea 1: Fast and slow pointers

Let the fast pointer move 2 bits at a time, and the slow pointer move one bit at a time.
When the fast pointer moves to the end, the slow pointer moves to the middle.

struct ListNode* middleNode(struct ListNode* head){
    
    
    struct ListNode* fast = head;
    struct ListNode* slow = head;
    // fast == NULL是到偶数链表末尾,fast == NULL是到奇数链表末尾
    while (fast && fast->next)
    {
    
    
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}

Complexity analysis:

  • Time complexity : O(n). Only need to execute the loop n/2 times.
  • Space complexity : O(1).

4. Palindrome linked list (234 questions)

Description of the problem: You are given a head node head of a singly linked list, please judge whether the linked list is a palindrome linked list. If yes, return true; otherwise, return false.
Example 1:
Input: head = [1,2,2,1]
Output: true
Example 2:
Input: head = [1,2,3,2,1]
Output: true
Example 3:
Input: head = [1, 2]
output: false

Tip:
The number of nodes in the linked list is greater than zero

Idea 1: After reversing the second half of the linked list, compare them in turn

The core logic is:
1. Find the intermediate node of the linked list through the fast and slow pointers.
Explanation: Use the endOfFirstHalf function to find the last node in the first half of the linked list (if it is 5 elements, it will return 3, if it is 4 elements, it will return 2).
Let the fast pointer fast move 2 bits at a time, and the slow pointer move one bit at a time. When the fast pointer moves to the end, the slow pointer finds the middle node.
2. Reverse the second half of the singly linked list.
Explanation: You can use the reverseList function written before, which can reverse the linked list and return the new head node.
3. Compare the first half of the linked list and the reversed linked list elements in the second half to determine whether it is a palindrome.
4. Call the reverseList function again to restore the original linked list.
5. Return the result.
Explanation: If the query reaches the end of the linked list, it returns true; otherwise, it returns false.

 struct ListNode* reverseList(struct ListNode* head){
    
    
    struct ListNode* cur = head;				// 定义指向当前结点的指针
    struct ListNode* prec = NULL;				// 定义指向前驱结点的指针
    while (cur)
    {
    
    
        struct ListNode* succ = cur->next;		// 定义指向后继结点的指针
        cur->next = prec;						// 让当前结点指向其前驱结点
        prec = cur;								// 让prec指针指向下一位
        cur = succ;								// 让cur指针指向下一位
    }
    return prec;								// 循环结束后,prec指向原本的最后一个结点,也就是反转后的第一个结点
}

struct ListNode* endOfFirstHalf(struct ListNode* head)
{
    
    
    struct ListNode* slow = head;		// 慢指针一次移动一位
    struct ListNode* fast = head;		// 快指针一次移动2位
    // fast->next == NULL,是奇数链表的情况,fast->next->next == NULL,是偶数链表的情况
    while (fast->next != NULL && fast->next->next != NULL) 
    {
    
    
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;						// 循环结束后,slow指针到达前半段最后一个结点
}

bool isPalindrome(struct ListNode* head){
    
    
    // 找前半部分尾结点并且反转后半部分链表
    struct ListNode* firstHalfEnd = endOfFirstHalf(head);
    struct ListNode* secondHalfStart = reverseList(firstHalfEnd->next);

    // 把链表前半部分和反转后的后半部分分为两部分,一一进行比较
    struct ListNode* list1 = head;
    struct ListNode* list2 = secondHalfStart;

    bool result = true;
    while (result && list2 != NULL)
    {
    
    
        if (list1->val != list2->val)
            result = false;
        list1 = list1->next;
        list2 = list2->next;
    }

    // 还原链表
    firstHalfEnd->next = reverseList(secondHalfStart);
    return result;
}

Complexity analysis:

  • Time complexity : O(n). It takes about 2n times.
  • Space complexity : O(1). It just modifies the pointing of the original linked list node, does not create a new linked list, and only requires constant item complexity.

5. Delete the last N node of the linked list (19 questions)

Problem description: Give you a linked list, delete the nth last node of the linked list, and return the head node of the linked list.
Example 1:
Input: head = [1,2,3,4,5], n = 2
Output: [1,2,3,5]
Example 2:
Input: head = [1], n = 1
Output: [ ]
Example 3:
Input: head = [1,2], n = 2
Output: [1]
Example 4:
Input: head = [1,2], n = 1
Output: [2]

Tip:
The number of nodes in the linked list is sz
1 <= n <= sz

Idea 1: Fast and slow pointers

Set the fast and slow double pointers, let the fast pointer go n steps first, and then let the slow pointer and the fast pointer go together. In this way, the slow pointer is n steps slower than the fast pointer.
When the fast pointer reaches the end, the slow pointer just goes to the last nth node.

Because deletion requires more of the predecessor pointer of the deleted pointer.
Moreover, if there is no dummy node, the case of deleting the first node has to be discussed separately.
Therefore, it is best to set a dummy node, and at the same time let the slow pointer point to the dummy node at the beginning, and the fast pointer points to the first data node (the head node of Lituo) at the beginning.
In this way, when fast goes to NULL, the slow pointer points to the predecessor node of the deleted node.
After that, you only need to perform a simple delete operation (let the predecessor of the deleted node connect to the successor, and release the space of the deleted node).
The returned result is the successor node of the dummy node.

struct ListNode* removeNthFromEnd(struct ListNode* head, int n){
    
    
	// 让哑结点连接头节点
    struct ListNode* dummyHead = (struct ListNode*)malloc(sizeof(struct ListNode));
    dummyHead->next = head;
    struct ListNode* slow = dummyHead;			// slow指针初始化为哑结点
    struct ListNode* fast = head;				// fast指针初始化为头节点,比slow指针快一步
    // 先让快指针走n步
    while(n--)
    	fast = fast->next;
    // 再让slow和fast指针一起走,直到fast指针走到NULL
    while (fast)
    {
    
    
        slow = slow->next;
        fast = fast->next;
    }
    struct ListNode* delete = slow->next;		// 记录被删除结点,也就是slow指针的下一个结点
    slow->next = delete->next;					// 让被删除结点的前驱连接后继,让它从链表中断开
    free(delete);								// 释放被删除结点空间
    return dummyHead->next;						// 返回哑结点的下一个结点
}

Complexity analysis:

  • Time complexity : O(n).
  • Space complexity : O(1).

6. Merge two ordered linked lists (21 questions)

Problem Description: Merge two ascending linked lists into a new ascending linked list and return. The new linked list is formed by splicing all the nodes of the given two linked lists.
Example 1:
Input: l1 = [1,2,4], l2 = [1,3,4]
Output: [1,1,2,3,4,4]
Example 2:
Input: l1 = [], l2 = []
Output: []
Example 3:
Input: l1 = [], l2 = [0]
Output: [0]

Idea 1: Iterative method


A dummy node is needed to help us return the connected linked list, otherwise we need to discuss whether the first node of Table 1 or Table 2 is smaller.
prec first points to the dummy node.
Let prec always connect the smaller current node in list1 and list2. If list1 or list2 is connected to the previous element, it will be moved back one bit.
Regardless of connecting table 1 or table 2, prec must be shifted one bit each time to ensure that it is always at the end of the merged new table (so that it is convenient to connect directly when encountering a new merged node).
If one of the tables moves to the end, it is directly connected to the remaining elements of the other table.

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
    
    
	// 设置临时哑结点
    struct ListNode* dummyHead = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode* prec = dummyHead;				// prec用来连接当前2个链表更小的结点
    while (list1 && list2)							
    {
    
    
        if (list1->val <= list2->val)				// 如果链表1的当前结点小于链表2的当前结点
        {
    
    
            prec->next = list1;						// 就让prec连接上链表1
            list1 = list1->next;					// 链表1的当前结点往后移动一位
        }
        else										// 如果链表2的当前结点小于链表1的当前结点
        {
    
    
            prec->next = list2;						// 就让prec连接上链表2
            list2 = list2->next;					// 链表2的当前结点往后移动一位
        }
        prec = prec->next;							// 只要连接一次,就让prec往后移动一位
    }
    prec->next = (list1 == NULL) ? list2 : list1;	// 如果其中一个链表移动到NULL,就让prec连接上另一个链表
    return dummyHead->next;							// 返回哑结点的下一个结点
}

Complexity analysis:

  • Time complexity : O(n+m). n and m are the respective lengths of the two singly linked lists.
  • Space complexity : O(1).

Idea 2: Recursive method
You only need to connect the smaller of the two nodes to other merged nodes.
The recursive method only needs to find the smaller of the first nodes of the two linked lists, and let it connect to other merged nodes.

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
    
    
	// 如果其中一个表为空,就直接返回另一个表
   if (list1 == NULL)
        return list2;
    if (list2 == NULL)
        return list1;
    // 只需要让最小的结点连接上其他已经被连接好的结点
    if (list1->val < list2->val)
    {
    
    
        list1->next = mergeTwoLists(list1->next, list2);
        return list1;
    }
    else
    {
    
    
        list2->next = mergeTwoLists(list1, list2->next);
        return list2;
    }
}

Complexity analysis:

  • Time complexity : O(n+m). n and m are the respective lengths of the two singly linked lists.
  • Space complexity : O(n+m). The recursive call needs to be called n+m times.

7. Split the linked list (interview question 02.04.)

Problem description: Given you a head node head of a linked list and a specific value x, please divide the linked list so that all nodes less than x appear before nodes greater than or equal to x. You don't need to preserve the initial relative positions of the nodes in each partition.
Example 1:
Input: head = [1,4,3,2,5,2], x = 3
Output: [1,2,2,4,3,5]
Example 2:
Input: head = [2,1 ,4,2], x = 2
output: [1,2,4,2]

Idea 1: Iterative method
The idea is very simple, define two head nodes, one is used to connect nodes smaller than x, and the other is connected to nodes greater than or equal to x.
Check each node of the original singly linked list in turn, if it is a small value, connect to the small value linked list; if it is a large value, connect to the large value linked list.
Every time a node is queried, let head point to the next node to be checked.
After the query is completed, each node has been stored in the small-value linked list and the large-value linked list respectively.
After that, you only need to make the end of the large-value linked list point to NULL, and then connect the end of the small-value linked list to the head node of the large-value linked list.
The division of the linked list is completed.
Note: Let the end of the large-value linked list point to NULL first, because the original linked list may not have a value greater than or equal to x, and the dummy nodes of the large-value linked list have not connected any nodes.
Therefore, it is necessary to make the end of the large-value linked list point to NULL before connecting the small-value linked list and the large-value linked list.

struct ListNode* partition(struct ListNode* head, int x){
    
    
    struct ListNode* dummyLess = (struct ListNode*)malloc(sizeof(struct ListNode));		// 新建哑结点存储小值结点
    struct ListNode* dummyLarge = (struct ListNode*)malloc(sizeof(struct ListNode));	// 新建哑结点存储大值结点
    struct ListNode* lessCur = dummyLess;							// lessCur指向小值链表的最后一个结点
    struct ListNode* largeCur = dummyLarge;							// largeCur指向大值链表的最后一个结点
    while (head != NULL)						
    {
    
    						
       if (head->val < x)											// 如果是小值,就连接上小值链表
       {
    
    
           lessCur->next = head;
           lessCur = lessCur->next;
       }
       else															// 如果是大值,就连接上大值链表
       {
    
    
           largeCur->next = head;
           largeCur = largeCur->next;
       }
       head = head->next;											// 连接完毕后,让head指向下一个待检查结点
    }
    largeCur->next = NULL;											// 先让大值链表的末尾指向NULL
    lessCur->next = dummyLarge->next;								// 再让小值链表末尾连接上大值链表的头
    return dummyLess->next;											// 返回小值链表的头结点
}

Complexity analysis:

  • Time complexity : O(n).
  • Space complexity : O(1).

8. Intersecting linked list (160 questions)

Given the head nodes headA and headB of two singly linked lists, please find and return the starting node where the two singly linked lists intersect. Returns null if there is no intersecting node between the two linked lists.
The diagram shows that the two linked lists intersect at node c1:
the title data ensures that there is no cycle in the entire chain structure.
Note that the linked list must maintain its original structure after the function returns the result.
Example 1:
insert image description here

Input: intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3
Output: Intersected at '8'
Explanation: The value of the intersection node is 8 (note that it cannot be 0 if the two linked lists intersect).
Counting from the respective headers, linked list A is [4,1,8,4,5] and linked list B is [5,6,1,8,4,5].
In A, there are 2 nodes before the intersection node; in B, there are 3 nodes before the intersection node.
— Note that the value of the intersecting node is not 1, because the nodes with a value of 1 in linked list A and linked list B (the second node in A and the third node in B) are different nodes. In other words, they point to two different locations in memory, and the node with value 8 in List A and List B (the third node in A, the fourth node in B) points to the same location in memory.

Example 2:
insert image description here

Input: intersectVal = 2, listA = [1,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
Output: Intersected at '2'
Explanation: The value of the intersected node is 2 (note that it cannot be 0 if the two linked lists intersect).
Counting from the respective headers, linked list A is [1,9,1,2,4], and linked list B is [3,2,4].
In A, there are 3 nodes before the intersection node; in B, there is 1 node before the intersection node.

Example 3:
insert image description here

Input: intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2 Output
: null
Explanation: Counting from the respective headers, linked list A is [2 ,6,4], linked list B is [1,5].
Since the two linked lists are disjoint, intersectVal must be 0, while skipA and skipB can be any value.
The two linked lists are disjoint, so null is returned.

Idea 1: Double pointer

The core problem of this question is that the number of steps required to find the intersecting node is different for the two linked lists starting from the head node.
Suppose the length of linked list A is a, the length of linked list B is b, and the number of intersecting nodes of linked lists is c.
Although the step size of the head node of linked list A and linked list B is different from the intersecting node.
But the sum of the two distances is fixed.
You can traverse from linked list A to the intersection node of linked list B, you need a+bc.
It is also possible to traverse from linked list B to the intersection node of linked list A, requiring b+ac.
At this time, both linked lists point to the intersection node.
If there is no intersecting node c=0, then both linked lists point to NULL at the same time.

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    
    
	// 考虑特殊情况,如果有链表为空,就不可能有相交结点,直接返回NULL
    if (headA == NULL || headB == NULL)
        return NULL;
    struct ListNode* pA = headA;
    struct ListNode* pB = headB;
    // 如果链表A和链表B到达相交结点步长相同,那么走指定步长后,pA会等于pB
    // 如果步长不同,那么pA和pB会走到NULL,此时让pA从headB处继续往下走,pB从headA处继续往下走
    // 如果有相交结点,它们走相同步长后一定会相遇pA=pB
    // 如果没相交结点,pA和pB会同时遍历完链表A和B,同时走到NULL,此时也结束循环
    while(pA != pB)
    {
    
    
        pA = (pA == NULL) ? headB : pA->next;
        pB = (pB == NULL) ? headA : pB->next;
    }
    return pA;			// 如果有相交结点,结束循环后,pA=pB=相交结点,如果没有相交结点,pA=pB=NULL
}

Complexity analysis:

  • Time complexity : O(n+m). m and n are the lengths of linked lists A and B respectively. In the worst case, linked lists A and B are still not intersected after traversing them.
  • Space complexity : O(1).

9. Ring linked list (141 questions)

Problem description: Give you a head node head of a linked list, and determine 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, return false.
Example 1:
insert image description here

Input: head = [3,2,0,-4], pos = 1
Output: true
Explanation: There is a ring in the linked list whose tail is connected to the second node.
Example 2:
insert image description here

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:
insert image description here

Input: head = [1], pos = 0
Output: true
Explanation: There is a ring in the linked list whose tail is connected to the first node.
Example 4:
insert image description here
Input: head = [1], pos = -1
Output: false
Explanation: There is no cycle in the linked list.

Idea 1: Fast and slow pointers

This question requires the basis of **"Floyd Judgment Circle Algorithm"** (also known as the Tortoise and the Hare Algorithm).
You can check my other blog post: "Floyd's circle judgment algorithm" (also known as the tortoise and the hare race algorithm)
set a fast pointer and a slow pointer to point to the head node, the fast pointer walks 2 steps at a time, and the slow pointer walks one step at a time.
According to Freud's circle method, if there is a circle <=> the speed pointer will definitely meet in the circle.
Therefore, it is only necessary to detect whether the fast and slow pointers meet. If the fast pointer reaches the end, it means that there is no loop. If the fast and slow pointers finally meet, there is a ring.

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)			// 如果快慢指针相遇,返回true
            return true;
    }
    return false;					// 如果跳出循环,说明fast到末尾,返回false
}

Complexity analysis:

  • Time complexity : O(n).
  • Space complexity : O(1).

10. Circular Linked List II (142 questions)


Problem 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.
Example 1:
insert image description here

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 whose tail is connected to the second node.
Example 2:
insert image description here

Input: head = [1,2], pos = 0
Output: Return the linked list node with index 0
Explanation: There is a ring in the linked list, whose tail is connected to the first node.
Example 3:
insert image description here

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

Idea 1: Fast and slow pointers

First find the meeting point where the fast and slow pointers meet for the first time.
Then let a new slow pointer point to the head of the linked list. The original slow pointer is still at the meeting point. When the two slow pointers meet again, the node is the starting point of the ring.

struct ListNode *detectCycle(struct ListNode *head) {
    
    
	// 如果链表是空表或者只有一个结点(且该节点的next不指向自身),直接返回false
     if (head == NULL || head->next == NULL)
        return false;
    // 快慢指针初始都指向头结点
    struct ListNode* fast = head;
    struct ListNode* slow = head;
    do
    {
    
    
    	// 快指针每次移动2步,慢指针每次移动一步
        fast = fast->next->next;
        slow = slow->next;
        // 如果fast能走到末尾,说明没环,返回false
        if (fast == NULL || fast->next == NULL)
            return false;
    }
    while (fast != slow);				// 如果fast == slow,说明走到快慢指针相遇点
    struct ListNode* newSlow = head;	// 让新的慢指针指向链表头结点
    // 假如链表头结点也是环的起点,两个慢指针一开始就相遇,程序不需要进入循环,直接返回该结点
    // 假如链表头结点不是环的起点,两个慢指针首次相遇的结点,就是环的起点
    while (slow != newSlow)
    {
    
    
        slow = slow->next;
        newSlow = newSlow->next;
    }
    return slow;			// 首次相遇的结点位置,就是环的起点
}

Complexity analysis:

  • Time complexity : O(n). n is the length of the linked list. The fast and slow pointers can take at most n steps when they meet for the first time, and take at most n steps when the two slow pointers meet for the second time. At most O(n)+O(n) = O(n)
  • Space complexity : O(1).

Guess you like

Origin blog.csdn.net/qq_983030560/article/details/128168983