The fifth day of algorithm review: double pointers--4

Table of contents

1. The middle node of the linked list

 1. Array:

Ideas and algorithms

Complexity analysis

2. Single pointer method

Complexity analysis

3. Fast and slow pointer method: (strange and cunning)

Ideas and algorithms

Complexity analysis

2. Delete the penultimate N node of the linked list

Preface

1. Calculate the length of the linked list:

Ideas and Algorithms

Complexity analysis

2. Stack

Ideas and Algorithms

Complexity analysis

3. Double pointers (fast and slow pointers)

Ideas and Algorithms

Complexity analysis

1. The middle node of the linked list

876. Middle of the linked list - LeetCode https://leetcode.cn/problems/middle-of-the-linked-list/

 1. Array:

Ideas and algorithms


The disadvantage of linked lists is that the corresponding elements cannot be accessed through subscripts. Therefore, we can consider traversing the linked list and putting the traversed elements into array A in sequence. If we traverse to N primes, then the length of the linked list and array is also N, and the corresponding intermediate node is A[N/2].
 

class Solution {
public:
    ListNode* middleNode(ListNode* head) {
        vector<ListNode*> A = {head};
        while (A.back()->next != NULL)
            A.push_back(A.back()->next);
        return A[A.size() / 2];
    }
};

Complexity analysis

  • Time complexity: O(N), where NN is the number of nodes in the given linked list.

  • Space complexity: O(N), that is,  A the space used by the array.

2. Single pointer method

We can perform space optimization on method 1 and omit array A.
We can traverse the linked list twice. During the first traversal, we count the number N of elements in the linked list; during the second traversal, when
we traverse to the N/2th element (the first node of the linked list is the 0th element), we return the element. Can.
 

class Solution {
public:
    ListNode* middleNode(ListNode* head) {
        int n = 0;
        ListNode* cur = head;
        while (cur != nullptr) {
            ++n;
            cur = cur->next;
        }
        int k = 0;
        cur = head;
        while (k < n / 2) {
            ++k;
            cur = cur->next;
        }
        return cur;
    }
};

Complexity analysis

  • Time complexity: O(N), where N is the number of nodes in the given linked list.

  • Space complexity: O(1), only constant space is needed to store variables and pointers.

3. Fast and slow pointer method: (strange and cunning)

Ideas and algorithms

We can continue to optimize method two, using two pointers slow and fast to traverse the linked list together. slow means taking one step at a time, fast means taking two steps at a time. Then when fast reaches the end of the linked list, slow must be in the middle.

class Solution {
public:
    ListNode* middleNode(ListNode* head) {
        ListNode* slow = head;
        ListNode* fast = head;
        while (fast != NULL && fast->next != NULL) {
            slow = slow->next;
            fast = fast->next->next;
        }
        return slow;
    }
};

Complexity analysis

Time complexity: O(N), where N is the number of nodes in the given linked list.

Space complexity: O(1), only constant space is needed to store two pointers, slow and fast.

2. Delete the penultimate N node of the linked list

19. Delete the Nth node from the last of the linked list - LeetCode https://leetcode.cn/problems/remove-nth-node-from-end-of-list/

Preface


When operating on a linked list, a common technique is to add a dummy node whose \textit{next}next pointer points to the head node of the linked list. In this way, we do not need to make special judgments on the head node.

For example, in this question, if we want to delete node y, we need to know the predecessor node x of node y, and point the pointer of x to the successor node of y. However, since the head node does not have a predecessor node, we need to make special judgments when deleting the head node. But if we add a dumb node, then the predecessor node of the head node is the dumb node itself. At this time, we only need to consider the general situation.

In particular, in some languages, memory needs to be managed by itself. Therefore, in the actual interview, we need to actively communicate with the interviewer to reach an agreement on the question of "whether the space corresponding to the deleted node needs to be released." The following code does not release space by default.

1. Calculate the length of the linked list:

Ideas and Algorithms

An easy way to think of is that we first traverse the linked list starting from the head node and get the length L of the linked list. Then we traverse the linked list starting from the head node. When we traverse to the L−n+1th node, it is the node we need to delete.

In order to be consistent with n in the question, the numbering of nodes starts from 1, and the head node is the node numbered 1.

In order to facilitate the deletion operation, we can traverse L−n+1 nodes starting from the dumb node. When the L−n+1th node is traversed, its next node is the node we need to delete, so we only need to modify the pointer once to complete the deletion operation.

class Solution {
public:
    int getLength(ListNode* head) {
        int length = 0;
        while (head) {
            ++length;
            head = head->next;
        }
        return length;
    }

    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummy = new ListNode(0, head);
        int length = getLength(head);
        ListNode* cur = dummy;
        for (int i = 1; i < length - n + 1; ++i) {
            cur = cur->next;
        }
        cur->next = cur->next->next;
        ListNode* ans = dummy->next;
        delete dummy;
        return ans;
    }
};

Complexity analysis

  • Time complexity: O(L), where LL is the length of the linked list.

  • Space complexity: O(1).

2. Stack

Ideas and Algorithms

We can also push all nodes onto the stack in sequence while traversing the linked list. According to the principle of "first in, last out" of the stack, the nth node we pop out of the stack is the node that needs to be deleted, and the node currently on the top of the stack is the predecessor node of the node to be deleted. In this way, the deletion operation becomes very convenient.

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummy = new ListNode(0, head);
        stack<ListNode*> stk;
        ListNode* cur = dummy;
        while (cur) {
            stk.push(cur);
            cur = cur->next;
        }
        for (int i = 0; i < n; ++i) {
            stk.pop();
        }
        ListNode* prev = stk.top();
        prev->next = prev->next->next;
        ListNode* ans = dummy->next;
        delete dummy;
        return ans;
    }
};

Complexity analysis

  • Time complexity: O(L), where LL is the length of the linked list.

  • Space complexity: O(L), where LL is the length of the linked list. Mainly due to stack overhead.

3. Double pointers (fast and slow pointers)

Ideas and Algorithms

We can also solve this problem without preprocessing the length of the linked list and using constant space.

Since we need to find the nth node from the bottom, we can use two pointers first and second to traverse the linked list at the same time, and first is n nodes ahead of second. When first traverses to the end of the linked list, second is exactly at the nth node from the bottom.

Specifically, initially first and second both point to the head node. We first use first to traverse the linked list, the number of traversals is n. At this time, there are n-1 nodes between first and second, that is, first is n nodes ahead of second.

After that, we use first and second at the same time to traverse the linked list. When first traverses to the end of the linked list (that is, first is a null pointer), second just points to the nth node from the bottom.

According to method one and method two, if we can get the predecessor node of the n-th node from the bottom instead of the n-th node from the bottom, the deletion operation will be more convenient. Therefore, we can consider pointing second to the dumb node initially, and the remaining operation steps remain unchanged. In this way, when first traverses to the end of the linked list, the next node of second is the node we need to delete.

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummy = new ListNode(0, head);
        ListNode* first = head;
        ListNode* second = dummy;
        for (int i = 0; i < n; ++i) {
            first = first->next;
        }
        while (first) {
            first = first->next;
            second = second->next;
        }
        second->next = second->next->next;
        ListNode* ans = dummy->next;
        delete dummy;
        return ans;
    }
};

Complexity analysis

  • Time complexity: O(L), where L is the length of the linked list.

  • Space complexity: O(1).

Guess you like

Origin blog.csdn.net/m0_63309778/article/details/126595292