Skip to content

LC143. Reorder List

Problem Description

LeetCode Problem 143: You are given the head of a singly linked-list. The list can be represented as:

L0 → L1 → … → Ln - 1 → Ln

Reorder the list to be on the following form:

L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …

You may not modify the values in the list's nodes. Only nodes themselves may be changed.

Clarification

  • Re-order the node not the value

Assumption

Solution

Approach - Stack

Use stack to store nodes. Then use one pointer, p1, go from head, and the other pointer, p2, go from end of nodes by popping from stack. Reorder the list, use p1 and p2. When p1 == p2 or p1.next = p2, finish the reordering

from collections import deque

class Solution:
    def reorderList(self, head: Optional[ListNode]) -> None:
        """
        Do not return anything, modify head in-place instead.
        """
        stack = deque()

        slow = head
        fast = head
        while fast and fast.next:
            stack.append(slow)
            slow = slow.next
            fast = fast.next.next

        front = head
        while front and stack:
            tail = stack.pop()

            if front is tail:
                front.next = None
                break
            elif front.next is tail:
                tail.next = None
                break
            else:
                nxt = front.next
                front.next = tail
                tail.next = nxt
                front = nxt

Complexity Analysis

  • Time complexity: \(O(n)\)
    It takes \(n\) steps to store nodes in stack and \(n/2\) steps to reorder the list. So the time complexity is \(O(n)\).
  • Space complexity: \(O(n)\)
    Use stack to store \(n\) nodes.

Approach - Split, Reverse and Merge

The problem can be solved by the following steps: 1. Split the list by two halves 2. Reverse the 2nd half 3. Merge the 1st half and the reversed 2nd half by reordering

class Solution:
    def reorderList(self, head: Optional[ListNode]) -> None:
        """
        Do not return anything, modify head in-place instead.
        """

        if head is None or head.next is None or head.next.next is None:
            return

        head_2ndHalf = self.splitListByHalf(head)

        head_2ndHalf = self.reverseList(head_2ndHalf)

        self.mergeList(head, head_2ndHalf)

    def splitListByHalf(self, head: Optional[ListNode]) -> Optional[ListNode]:
        """
        Split the list by half and return the head of 2nd half.
        The 1st half of list will end with None
        """

        slow = head
        fast = head
        while fast and fast.next:
            prev = slow
            slow = slow.next
            fast = fast.next.next

        prev.next = None # 1st half of list end with None
        return slow

    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        prev = None
        curr = head

        while curr:
            nxt = curr.next
            curr.next = prev
            prev = curr
            curr = nxt

        return prev

    def mergeList(self, head1: Optional[ListNode], head2: Optional[ListNode]) -> None:
        p1 = head1
        p2 = head2

        while p1 and p2:
            nxt1 = p1.next
            nxt2 = p2.next
            p1.next = p2
            if nxt1:
                p2.next = nxt1
            p1 = nxt1
            p2 = nxt2
class Solution {
    public void reorderList(ListNode head) {
        // If empty, 1 or 2 nodes, no need to reorder and return
        if (head == null || head.next == null || head.next.next == null) return;

        ListNode h2 = splitListByHalf(head);

        // Reverse the 2nd half
        ListNode l2 = reverseList(h2);

        mergeList(head, l2);

    }

    private ListNode splitListByHalf(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        ListNode prev = head;

        // Find the start of 2nd half use a slow and fast pointers
        // After while loop, slow is the start node of the 2nd half
        while (fast != null && fast.next != null) {
            prev = slow;
            slow = slow.next;
            fast = fast.next.next;
        }
        prev.next = null; // the end node of the first half point to null
        return slow;
    }

    private ListNode reverseList(ListNode head) {
        ListNode prev = null;
        ListNode curr = head;
        ListNode next;

        while (curr != null) {
            next = curr.next;
            curr.next = prev;
            prev = curr;
            curr = next;
        }

        return prev;
    }

    private void mergeList(ListNode l1, ListNode l2) {
        // Pairing
        // number of nodes in 2nd half == number of nodes in 1st half (even number nodes)
        // number of nodes in 2nd half == number of nodes in 1st half + 1 (odd number nodes)
        ListNode p1 = l1;
        ListNode p2 = l2;
        ListNode n1;  // hold next node of the first half
        ListNode n2;  // hold next node of the 2nd half
        while (p1 != null) {
            n1 = p1.next;
            n2 = p2.next;
            p1.next = p2;
            if (n1 != null) p2.next = n1;
            p1 = n1;
            p2 = n2;
        }

    }
}

Complexity Analysis

  • Time complexity: \(O(n)\)
    It takes \(n/2\) steps to split the list, \(n/2\) steps to reverse the list, and \(n/2\) steps to merge the list. So the total steps are \(3n/2\) and time complexity is \(O(n)\)
  • Space complexity: \(O(1)\)
    Just use several pointers

Comparison of Different Approaches

The table below summarize the time complexity and space complexity of different approaches:

Approach Time Complexity Space Complexity
Approach - Stack \(O(n)\) \(O(n)\)
Approach - Split, reverse, merge \(O(n)\) \(O(1)\)

Test