[typescript] linked list algorithm question type and method summary

Linked list questions have been brushed about 20 times, and some rules have been summarized and shared with you

Problem solving ideas

The main ideas are recursion and iteration, which also includes some skilled operations, such as fast and slow pointers. These summarized problem-solving ideas should not be a problem to deal with most linked list problems

Case 1: the penultimate node

Reference questions:

  • The penultimate node in the sword finger offer22 lin k ed list is simple
  • LC19 delete the penultimate node in the linked list

The main idea is the fast and slow pointer. Let the fast pointer take k steps first and then move forward together. When the fast pointer is finished, the position of the slow pointer is the penultimate node

const getKthFromEnd: ListNode | null = (head: ListNode | null, k: number) => {
    let slow: ListNode = head, fast: ListNode = head
    for (let i = 0; i < k; i++){
        fast = fast.next
    }
    while (fast){
        fast = fast.next
        slow = slow.next
    }
    return slow
};

This idea can then be applied to delete the penultimate node, as long as the reference of the penultimate node n+1 is found

const removeNthFromEnd: ListNode | null = (head: ListNode | null, n: number) => {
    let dummy: ListNode = new ListNode(-1)
    dummy.next = head
    const p: ListNode = getKthFromEnd(dummy, n+1)
    p.next = p.next.next
    return dummy.next
}

Case 2: circular linked list / finding the midpoint of the linked list

Reference questions:

  • LC141 ring linked list
  • LC142 ring linked list II
  • LC876 intermediate node of linked list

The second common application scenario of fast and slow pointer. In this type of questions, the fast pointer takes two steps and the slow pointer takes one step. Because the methods of fast and slow pointers converge, we put these two types of questions together
For circular linked list 1, it can be understood that each time the fast pointer takes two steps and the slow pointer takes one step. Once entering the ring, the slow pointer and the fast pointer will eventually meet. If they do not meet from beginning to end, there is no ring
The code is as follows

const hasCycle: boolean = (head: ListNode | null) => {
    let slow: ListNode = head
    let fast: ListNode = head
    while (fast && fast.next) {
        slow = slow.next
        fast = fast.next.next
        if (slow === fast){
            return true
        }
    }
    return false
};

For ring linked list II, the title requires to return the starting point index of the ring. This can first judge whether there is a ring. If there is a ring, wait for the next operation, and null will be returned if there is no ring
If there is a ring, start at the node where the fast and slow pointers coincide, let the slow pointer return to the head node, and the fast and slow pointers advance at the same speed. If the fast and slow pointers meet again, this point is the starting point of the ring.
How do you understand this sentence? First of all, we should admit that when the fast pointer takes 2k steps, the slow pointer just takes K steps. At this time, the fast pointer meets the slow pointer. Then the K steps that the fast pointer takes more than the slow pointer are actually an integer multiple of the ring length. Assuming that the first meeting node is m nodes away from the starting position of the ring, and the length of a ring is exactly k steps, the distance between the slow pointer and the starting point of the ring is K-M, and the distance between the fast pointer and the starting point of the ring is k-m. The idea here refers to labuladong's algorithm sketch.

const detectCycle: ListNode | null = (head: ListNode | null) => {
    let slow: ListNode = head
    let fast: ListNode = head
    while (fast && fast.next) {
        slow = slow.next
        fast = fast.next.next
        if (fast === slow){
            break;
        }
    }
    if (fast === null || fast.next === null) {
        return null
    }
    slow = head
    while (slow !== fast) {
        slow = slow.next
        fast = fast.next
    }
    return slow
};

Finding the midpoint of the linked list is also very similar. It is also one step for the slow pointer and two steps for the fast pointer. When the fast pointer comes, the slow pointer just comes to the middle. Here, we need to consider the parity of the length of the linked list, which may have some impact

Case 3: reverse linked list

Reference questions:

  • LC206 reverse linked list is simple
  • LC234 palindrome linked list is simple
  • LC92 reverse linked list II medium
  • It is difficult to reverse the linked list of LC25 K

Take the simplest inverted linked list as an example. Given a header node, an inverted linked list is returned. The template of typescript is as follows. Using the iterative method, first save cur.next to a new variable, and then point to pre every time you modify cur, moving pre and cur forward.

const reverse: ListNode | null = (head: ListNode | null) => {
    let pre: ListNode = null, cur: ListNode = head
    while (cur){
        let temp = cur.next
        cur.next = pre
        pre = cur
        cur = temp
    }
    return pre
}

The palindrome linked list is very simple based on the above example. It is an example of finding the midpoint + reverse linked list in the linked list. The code is as follows:

const isPalindrome: boolean = (head: ListNode | null) => {
    let slow: ListNode = head
    let fast: ListNode = head
    while (slow){
        if (fast === null) { //even numbers
            break
        } else if (fast.next === null){ //Odd number
            slow = slow.next
            break
        }
        slow = slow.next
        fast = fast.next.next
    }
    let p: ListNode = head // Compare p and slow
    slow = reverse(slow)
    while (slow) {
        if (slow.val !== p.val){
            return false
        }
        slow = slow.next
        p = p.next
    }
    return true
};

Other types of topics

Representative ones include merging linked lists, deleting duplicate elements of linked lists, intersecting linked lists, exchanging linked list nodes in pairs, and reversing linked lists in groups of K. most of these can be done by recursion. I won't elaborate here. Here we mainly use several classic cases to analyze some common linked list questions. Please forgive me if there are mistakes

Tags: TypeScript Algorithm leetcode linked list

Posted on Mon, 06 Dec 2021 21:57:02 -0500 by oskare100