Review of Algorithm Questions: A Collection of Double Pointer Linked List Series

1. Merge two ordered linked lists

 train of thought

  • Create a new linked list pointing to empty to store the merged linked list, and the p pointer points to the linked list.
  • Create a double pointer and point to two linked lists respectively, represented by p1 and p2
  • The while loop judges the size of the data pointed to by the two pointers in turn, and assigns the minimum value to the current value of the p pointer. Point the pointer of the minimum value to the next node, and point the p pointer to the next node. Exit the while loop when p1 or p2 points to a null pointer
  • Determine if the p1 pointer is not empty, point p->next to p1->next
  • Determine if the p2 pointer is not empty, point p->nex to p2->next

the code

ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
    // 虚拟头结点
    ListNode* dummy = new ListNode(-1), *p = dummy;
    ListNode* p1 = l1, *p2 = l2;
    
    while (p1 != NULL && p2 != NULL) {
        // 比较 p1 和 p2 两个指针
        // 将值较小的的节点接到 p 指针
        if (p1->val > p2->val) {
            p->next = p2;
            p2 = p2->next;
        } else {
            p->next = p1;
            p1 = p1->next;
        }
        // p 指针不断前进
        p = p->next;
    }
    
    if (p1 != NULL) {
        p->next = p1;
    }
    
    if (p2 != NULL) {
        p->next = p2;
    }
    
    return dummy->next;
}

2. Decomposition of single linked list

Given the head node head of a linked list and a specific value x, divide the linked list so that all nodes less than x are placed before the x node, and those greater than or equal to x are placed after the x node. The relative position of each node in the two partitions is preserved.

train of thought

  • Create two linked lists, respectively store the number less than x, and the node with the number greater than or equal to x, the pointers are p1, p2
  • The node that merges p1 and p2 is what you want.

Note that each while loop in the code first assigns p->next to the new linked list of tmp, and then sets the p->next node to nullptr, because the p pointer is directly assigned to p1->next every time, or p2->next. In order to prevent the linked list of p1 and p2 from pointing to the chaotic p node, it is necessary to set the p->next node to nullptr

 the code


ListNode* partition(ListNode* head, int x) {
    // 存放小于 x 的链表的虚拟头结点
    ListNode* dummy1 = new ListNode(-1);
    // 存放大于等于 x 的链表的虚拟头结点
    ListNode* dummy2 = new ListNode(-1);
    // p1, p2 指针负责生成结果链表
    ListNode* p1 = dummy1, * p2 = dummy2;
    // p 负责遍历原链表,类似合并两个有序链表的逻辑
    // 这里是将一个链表分解成两个链表
    ListNode* p = head;
    while (p != nullptr) {
        if (p->val >= x) {
            p2->next = p;
            p2 = p2->next;
        } else {
            p1->next = p;
            p1 = p1->next;
        }
        // 断开原链表中的每个节点的 next 指针
        ListNode* temp = p->next;
        p->next = nullptr;
        p = temp;
    }
    // 连接两个链表
    p1->next = dummy2->next;

    return dummy1->next;
}

3. Merge k ordered linked lists

Given an array of linked lists, each linked list is sorted in ascending order. Please merge all linked lists into one ascending linked list and return it.

 train of thought

  • Create a minimum heap and store the head node of each linked list into the heap
  • The while loop judges, each time a current minimum value is taken from the heap, and the node is placed in the result linked list. Then, if the next node of the linked list that takes out the minimum value is not empty, point it to the next node, and then put it into the minimum heap. When there is no linked list in the minimum heap, exit the while loop.

Time complexity: O(Nlogk), k is the number of linked lists, and N is the total number of nodes in these linked lists.

the code

ListNode* mergeKLists(vector<ListNode*>& lists) {
    if (lists.empty()) return nullptr;
    // 虚拟头结点
    ListNode* dummy = new ListNode(-1);
    ListNode* p = dummy;
    // 优先级队列,最小堆
    priority_queue<ListNode*, vector<ListNode*>, function<bool(ListNode*, ListNode*)>> pq(
        [] (ListNode* a, ListNode* b) { return a->val > b->val; });
    // 将 k 个链表的头结点加入最小堆
    for (auto head : lists) {
        if (head != nullptr) {
            pq.push(head);
        }
    }

    while (!pq.empty()) {
        // 获取最小节点,接到结果链表中
        ListNode* node = pq.top();
        pq.pop();
        p->next = node;
        if (node->next != nullptr) {
            pq.push(node->next);
        }
        // p 指针不断前进
        p = p->next;
    }
    return dummy->next;
}

4. Find the k-th node from the bottom of the singly linked list

Basic version: Traverse the linked list twice, the first pass knows the length n of the linked list, the second pass, the penultimate k node is the positive number n-k+1 node, and output the n-k+1 node.

Double pointer version:

  • See the first pointer p1, and go to k nodes,
  • Then create the second pointer p2, p2 points to the head pointer
  • The p1 and p2 pointers advance to next synchronously. When the p1 pointer points to the nullptr at the end of the original linked list p, p2 just goes to the n-k+1th position.

 the code

// 返回链表的倒数第 k 个节点
ListNode* findFromEnd(ListNode* head, int k) {
    ListNode* p1 = head;
    // p1 先走 k 步
    for (int i = 0; i < k; i++) {
        p1 = p1 -> next;
    }
    ListNode* p2 = head;
    // p1 和 p2 同时走 n - k 步
    while (p1 != nullptr) {
        p2 = p2 -> next;
        p1 = p1 -> next;
    }
    // p2 现在指向第 n - k + 1 个节点,即倒数第 k 个节点
    return p2;
}

To expand, when you need to delete the last nth node of the linked list, you can directly use the above code to find the last n+1th node x, and then point the x->next pointer to x->next->next.

5, Find the midpoint of the singly linked list

Ideas
In the same way, create two pointers slow and fast to point to the head node head of the linked list respectively.

Slow advances one step each time, and fast advances two steps each time. When fast reaches the end of the head linked list, slow is at the midpoint of the linked list.

Note: If there are two midpoints in the linked list, the later node will be returned.

the code

ListNode* middleNode(ListNode* head) {
    // 快慢指针初始化指向 head
    ListNode* slow = head;
    ListNode* fast = head;
    // 快指针走到末尾时停止
    while (fast != nullptr && fast->next != nullptr) {
        // 慢指针走一步,快指针走两步
        slow = slow->next;
        fast = fast->next->next;
    }
    // 慢指针指向中点
    return slow;
}

6. Determine whether the linked list contains a ring, and the starting point of the ring

train of thought

Or through the fast and slow pointer. The slow pointer advances one step, and the fast advances two steps.

If the fast pointer finally encounters a null pointer, it means that there is no ring in the linked list; if the fast finally meets slow, it means that fast exceeds slow by one circle, indicating that there is a ring in the linked list.

bool hasCycle(ListNode* head) {
    // 初始化快慢指针,指向头结点
    ListNode* slow = head;
    ListNode* fast = head;
    // 快指针到尾部时停止
    while (fast && fast->next) {
        // 慢指针走一步,快指针走两步
        slow = slow->next;
        fast = fast->next->next;
        // 快慢指针相遇,说明含有环
        if (slow == fast) {
            return true;
        }
    }
    // 不包含环
    return false;
}

Judging the starting point of the ring: only need to repoint the slow node to the head node when fast and slow meet, and then the slow and fast pointers start to move forward synchronously. When they meet again, it is the starting node of the ring.

As shown in the diagram below, for the first encounter, slow takes k steps, fast takes 2k steps, and the length of the ring is k. Assuming that the distance between the meeting node and the starting point of the ring is n, then the distance from the head node to the slow position is k, and from the head node to the ring start point begin is kn. The length of the ring minus the distance from the start of the ring to slow is also -n. So from the place where they met for the first time, let slow point to the head node again, and fast and slow move forward synchronously. When they meet again, they will be the starting node of the ring.

the code

ListNode* detectCycle(ListNode* head) {
    ListNode* fast = head;
    ListNode* slow = head;
    while (fast != nullptr && fast->next != nullptr) {
        fast = fast->next->next;
        slow = slow->next;
        if (fast == slow) break;
    }
    // 上面的代码类似 hasCycle 函数
    if (fast == nullptr || fast->next == nullptr) {
        // fast 遇到空指针说明没有环
        return nullptr;
    }

    // 重新指向头结点
    slow = head;
    // 快慢指针同步前进,相交点就是环起点
    while (slow != fast) {
        fast = fast->next;
        slow = slow->next;
    }
    return slow;
}

7. Determine whether two linked lists intersect

As shown in the figure below, given linked list A is a1->a2->c1->c2, and linked list B is b1->b2->b3->c1->c2, how to judge whether A and B intersect?

 train of thought

The difficulty is that since the lengths of the two linked lists may be different, the nodes between the two linked lists cannot correspond. So the key to solving the problem is to make p1 and p2 point to A and B respectively in a certain way, so that they can reach node c1 at the same time.

We can   start traversing the linked list after  p1 traversing the linked list  , and   start traversing the linked list after   traversing the linked list  , which is equivalent to "logically" connecting the two linked lists together.ABp2BA

If splicing is performed in this way, and can  enter the common part p1 at  p2 the same time, that is, reach the intersection node at the same time  c1:

 the code

// 求链表的交点
ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) {
    // p1 指向 A 链表头结点,p2 指向 B 链表头结点
    ListNode *p1 = headA, *p2 = headB;
    while (p1 != p2) {
        // p1 走一步,如果走到 A 链表末尾,转到 B 链表
        if (p1 == nullptr) p1 = headB;
        else               p1 = p1->next;
        // p2 走一步,如果走到 B 链表末尾,转到 A 链表
        if (p2 == nullptr) p2 = headA;
        else               p2 = p2->next;
    }
    return p1; // 返回交点
}

Another way of thinking is to first get the length of the linked list of A and B, and then whoever is longer will take n steps first to make it reach the last node at the same time. Then A and B move forward synchronously, exit the program at the same time, return true, or exit at the end at the same time, return false.

the code

ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) {
    int lenA = 0, lenB = 0;
    // 计算两条链表的长度
    for (ListNode *p1 = headA; p1 != nullptr; p1 = p1->next) {
        lenA++;
    }
    for (ListNode *p2 = headB; p2 != nullptr; p2 = p2->next) {
        lenB++;
    }
    // 让 p1 和 p2 到达尾部的距离相同
    ListNode *p1 = headA, *p2 = headB;
    if (lenA > lenB) {
        // p1 先走过 lenA-lenB 步
        for (int i = 0; i < lenA - lenB; i++) {
            p1 = p1->next;
        }
    } else {
        // p2 先走过 lenB-lenA 步
        for (int i = 0; i < lenB - lenA; i++) {
            p2 = p2->next;
        }
    }
    // 看两个指针是否会相同,p1 == p2 时有两种情况:
    // 1、要么是两条链表不相交,他俩同时走到尾部空指针
    // 2、要么是两条链表相交,他俩走到两条链表的相交点
    while (p1 != p2) {
        p1 = p1->next;
        p2 = p2->next;  
    }
    return p1;
}

Reference: Two-pointer technique kills seven linked lists Topic:: Labuladong's Algorithm Cheat Sheet

Guess you like

Origin blog.csdn.net/u010420283/article/details/130000992