InterviewKata
Pattern 05 of 24

LinkedList In-place Reversal

Reverse a linked list (or just a piece of it) by flipping each arrow to point backwards, one node at a time, using three pointers and no extra memory.

1. What is this pattern for?

Picture a conga line where each dancer holds the shoulder of the person in front. To make the line face the other way, you walk down it and tell each dancer, one by one, to let go and grab the person who was behind them instead. You only ever need to remember three people: the one you just turned around (prev_node), the one you're turning now (current), and the next one in line (next_node), so you don't lose the rest of the line when current lets go.

A singly linked list is a chain of nodes where each node knows only its next. You can't index into it or walk backwards. The in-place reversal pattern rewires the next pointers so the chain points the other way, in O(n) time and O(1) extra space (no copying into an array).

It's the building block for: reversing the whole list, reversing positions left..right, reversing every group of k, swapping pairs, and "compare the first half with the reversed second half" problems (palindrome, twin sums, reorder).

2. How it works (bisected)

  1. Set up. prev_node = None (the new tail will point at nothing), current = head.
  2. Save. next_node = current.next. Do this first, because the next line destroys your only link to the rest of the list.
  3. Flip. current.next = prev_node. The arrow now points backwards.
  4. Advance both. prev_node = current, then current = next_node. Repeat steps 2–4 until current is None.
  5. Return prev_node: it's the old tail, now the new head.
  6. Level up: reverse just a part. Use a dummy node before the head so "the node before the part" always exists (even when the part starts at the head). Remember the node just before the part and the part's first node (it'll become the part's tail), reverse the part, then stitch both ends back.

Naming note: the code on this page uses the long names prev_node, current and next_node. The animations below have little room for labels, so they shorten them to prev, cur and nxt. They are the same three pointers.

3. Seeing a problem: how to spot In-place Reversal

Trigger words

  • "reverse the linked list", "reverse from position left to right"
  • "reverse nodes in k-group", "swap every two adjacent nodes"
  • "in place", "O(1) extra memory", "do not modify the values, only the nodes"
  • "palindrome linked list", "twin sum", "reorder list", "add two numbers II" (when the digits are stored in an inconvenient order)

Shape of the input

  • A singly linked list (ListNode with val and next).
  • You need to traverse it in the opposite order, or reorder blocks of nodes.
  • Brute-force smell: "copy all values into a Python list, reverse/re-arrange, write them back". That's O(n) extra space, and often forbidden ("don't change values").
The one question to ask"Would this be easy if (part of) the list pointed the other way?" If yes, flip it in place with prev_node / current / next_node.

4. When to use it, and when not to

Use In-place Reversal when…Prefer something else when…
You must reverse all or part of a linked list in O(1) spaceSpace doesn't matter and clarity does: push nodes onto a stack or into an array
Comparing the first half with the second half backwards (palindrome, twin sum)You only need the middle or a cycle: that's Fast & Slow Pointers (often used together: find middle, then reverse)
Reordering blocks of nodes (k-group, swap pairs)You have an array: reversing a slice is just Two Pointers swapping from both ends
Reversal must not allocate new nodesYou're merging sorted lists: use a dummy + splice (or a heap for k lists, see Top-K)

Cost: O(n) time (each node's arrow is flipped once), O(1) extra space. The recursive version is also O(n) time but uses O(n) call-stack space.

5. The Python template (memorize this)

# A) Reverse the whole list with three pointers.
def reverse(head):
    # prev_node = the front of the part we have ALREADY reversed.
    # Nothing is reversed yet, so it starts as None
    # (the old head becomes the new tail, and a tail points at None).
    prev_node = None
    # current = the node whose arrow we are about to flip.
    current = head
    # Keep going until we walk off the end of the list.
    while current is not None:
        # 1. SAVE the rest of the list. The next line overwrites
        #    current.next, and without this we would lose the rest forever.
        next_node = current.next
        # 2. FLIP the arrow: current now points backwards.
        current.next = prev_node
        # 3. STEP prev_node forward: current is the new front of the reversed part.
        prev_node = current
        # 4. STEP current forward: move on to the saved rest of the list.
        current = next_node
    # current is None now. prev_node is the old tail, which is the new head.
    return prev_node


# B) Reverse only positions left..right (1-indexed) with a dummy node
#    and "head insertion".
def reverse_between(head, left, right):
    # A dummy node in front of head guarantees that a node BEFORE the part
    # always exists, even when left == 1.
    dummy = ListNode(0, head)
    # Walk left - 1 steps so that 'before' stops just before position left.
    before = dummy
    for step in range(left - 1):
        before = before.next
    # tail = the first node of the part. It never moves itself;
    # the other nodes jump over it, so it ends up LAST in the part.
    tail = before.next
    # right - left moves are enough to reverse the part.
    for move in range(right - left):
        # The node right after tail is the one we move this time.
        moved = tail.next
        # Unhook it: tail now skips over it.
        tail.next = moved.next
        # Insert it at the FRONT of the part, right after 'before'.
        moved.next = before.next
        before.next = moved
    # dummy.next is the real head (it changed if left was 1).
    return dummy.next

The same template, explained for a beginner:

What the code is doing, line by line:

  1. Template A, set up: prev_node = None because nothing has been reversed yet (and the old head, which becomes the new tail, must point at None). current = head is the first node whose arrow we will flip.
  2. while current is not None: repeat until we have walked off the end of the list.
  3. Save: next_node = current.next remembers the rest of the list. This must come first, because the next line overwrites current.next.
  4. Flip: current.next = prev_node turns the arrow around, so current now points back at the already-reversed part.
  5. Step, step: prev_node = current (the reversed part grew by one node) and current = next_node (move on to the saved rest).
  6. return prev_node: when current is None, prev_node is the old last node, which is the new head.
  7. Template B, set up: dummy = ListNode(0, head) puts a fake node in front, so there is always a node before the part, even when left == 1.
  8. for step in range(left - 1): before = before.next walks to the node just before position left.
  9. tail = before.next is the first node of the part. It never moves itself: the other nodes jump in front of it, so it ends up last.
  10. for move in range(right - left): each move takes moved = tail.next, unhooks it (tail.next = moved.next) and inserts it at the front of the part (moved.next = before.next, then before.next = moved).
  11. return dummy.next: the real head (it is a different node if left was 1).

Dry run of template A on head = 1 → 2 → 3 (each row is the state after one save/flip/step/step round):

Stepcurrent (flipped)next_node (saved)reversed part (from prev_node)rest (from current)
start——None1 → 2 → 3
11212 → 3
2232 → 13
33None3 → 2 → 1None
endNone—3 → 2 → 1None → loop stops, return prev_node = 3

Dry run of template B on head = 1 → 2 → 3 → 4 → 5 → 6, left = 2, right = 5 (the same input as the second animation in section 2):

Stepbeforetailmovedlist (from dummy.next)
setup12—1 → 2 → 3 → 4 → 5 → 6
move 11231 → 3 → 2 → 4 → 5 → 6
move 21241 → 4 → 3 → 2 → 5 → 6
move 31251 → 5 → 4 → 3 → 2 → 6
returndummy.next → 1 → 5 → 4 → 3 → 2 → 6

6. How to remember it forever

Memory hook: "Save, flip, step, step"Four lines, always in this order: next_node = current.next → current.next = prev_node → prev_node = current → current = next_node. Each line's left side is the previous line's right side, like a chain.
How this hook works: what “Save, flip, step, step” means

What the slogan means, phrase by phrase

“Save”
next_node = current.next: remember the rest of the line before you let go of it.
“flip”
current.next = prev_node: turn this arrow around so it points backwards.
“step”
prev_node = current: the node you just flipped is now the front of the reversed part.
“step”
current = next_node: move on to the part you saved.

Why this picture fits the pattern: Reversing a list is four pointer moves repeated, and the order is everything: flip before you save and the rest of the list is lost. The slogan is the four lines in order, one word per line.

The story → the code

In the storyIn the code
The dancer you just turnedprev_node
The dancer you're turning nowcurrent
The next dancer in linenext_node
Save, flip, step, stepthe four lines inside while current is not None
The new front of the linereturn prev_node

Watch it on numbers

Reverse 1 → 2 → 3. Start with prev_node = None and current = 1.

  1. Save 2. Flip 1 → None. Step: prev_node = 1, current = 2. Reversed so far: 1.
  2. Save 3. Flip 2 → 1. Step: prev_node = 2, current = 3. Reversed so far: 2 → 1.
  3. Save None. Flip 3 → 2. Step: prev_node = 3, current = None. Reversed: 3 → 2 → 1.
  4. Notice the chain: each line's left side is the previous line's right side (next_node, current.next, prev_node, current).

The trap it saves you from

Skipping "save" (the rest of the list is lost the moment you flip), or returning head, which is now the tail. The new head is prev_node.

It still works when…

Reverse a sublist (LeetCode 92) or k nodes at a time: put a dummy node in front, walk to the node before the section, run the same four lines k times, then reconnect both ends.

Picture

A row of dominoes with arrows. Your hand moves left to right, turning each arrow around. Behind your hand is a reversed list (prev_node is its head); ahead is the untouched rest (next_node).

Chant

"Save the next, point back, prev_node steps up, current steps on."

Dummy rule

"If the head might change, start with a dummy." Then return dummy.next.

7. LeetCode problems, solved step by step

New to this pattern? Start with theseSolve 206. Reverse Linked List (iterative + recursive), then 21. Merge Two Sorted Lists, then 92. Reverse Linked List II. They are marked Start here below. Read the explanation, press Play on the step-through, then close the page and solve it yourself on LeetCode.
Hard 25. Reverse Nodes in k-Group Google Meta Amazon Cisco Microsoft Linked List

Problem: Reverse the nodes of the list k at a time. If the last group has fewer than k nodes, leave it as it is. Only change links, not values.

Example 1
Input: head = [1,2,3,4,5], k = 2
Output: [2,1,4,3,5]
Example 2
Input: head = [1,2,3,4,5], k = 3
Output: [3,2,1,4,5]

Constraints: The number of nodes in the list is n. · 1 ≤ k ≤ n ≤ 5000 · 0 ≤ Node.val ≤ 1000

Hint 1

Handle the list one group of k nodes at a time. Check that a full group exists before touching it.

Hint 2

Reverse the group with prev/cur/next, then reconnect: the node before the group points to the new group head, and the old group head (now its tail) points to what came after.

From brute force to the pattern: Copying values into an array, reversing chunks and rebuilding uses O(n) space. Reversing each full group in place with careful reconnection is O(n) time and O(1) space; a dummy node avoids special-casing the first group.

Spot it: "reverse" + "k at a time" + "only nodes may be changed": repeated sub-list reversal with stitching.

Key insight: keep group_prev (the node before the group). First check that k nodes exist (find kth). Reverse the group with prev_node initialised to group_next instead of None: that way the group's old first node automatically points to the rest of the list. Then hook group_prev.next = kth and move group_prev to the old first node (now the group's tail).

Approach:

  1. Make a dummy before head; group_prev = dummy.
  2. Walk k steps from group_prev to find kth. If you hit None, return dummy.next.
  3. Save group_next = kth.next. Reverse the k nodes with prev_node starting at group_next.
  4. Stitch: group_prev.next = kth (the new front). The old first node is now the group's tail, so it becomes the next group_prev.
  5. Repeat.
class Solution:
    def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
        # The head changes after the first group, so put a dummy in front.
        dummy = ListNode(0, head)
        # group_prev = the node just BEFORE the group we are working on.
        group_prev = dummy
        while True:
            # --- 1) Is there a full group of k nodes? ---
            # Walk k steps from group_prev. We land on the k-th node of the group.
            kth = group_prev
            for step in range(k):
                kth = kth.next
                if kth is None:
                    # Fewer than k nodes are left: leave them as they are.
                    return dummy.next
            # The first node AFTER this group (it may be None).
            group_next = kth.next
            # The group's first node. After reversing, it will be the group's LAST node.
            group_first = group_prev.next

            # --- 2) Reverse the k nodes of this group (save, flip, step, step). ---
            # Trick: prev_node starts at group_next instead of None, so the old
            # first node automatically ends up pointing at the rest of the list.
            prev_node = group_next
            current = group_first
            while current is not group_next:
                next_node = current.next      # save the rest
                current.next = prev_node      # flip the arrow
                prev_node = current           # step prev_node
                current = next_node           # step current

            # --- 3) Stitch the front of the group back in. ---
            # kth was the group's last node; now it is the group's first node.
            group_prev.next = kth
            # The old first node is now the group's tail, so it is
            # the node BEFORE the next group.
            group_prev = group_first

What the code is doing, line by line:

  1. dummy = ListNode(0, head) and group_prev = dummy: the head will change after the first group, so we keep a fake node in front. group_prev always sits just before the group we are working on.
  2. while True: handle one group per round. The only way out is the return inside.
  3. Check for a full group: starting at group_prev, the for step in range(k) loop walks k nodes. If it falls off the end (kth is None), fewer than k nodes are left, so we stop and return dummy.next without touching them.
  4. group_next = kth.next is the first node after the group; group_first = group_prev.next is the group's first node (it will become the group's last node).
  5. Reverse the group: the usual save / flip / step / step loop, with one trick: prev_node starts at group_next instead of None. So when group_first is flipped, it points straight at the rest of the list and the back end is already stitched. The loop stops when current reaches group_next.
  6. group_prev.next = kth stitches the front: the node before the group now points at the old last node, which is the new first node.
  7. group_prev = group_first: the old first node is now the group's tail, so it is the node before the next group.

Dry run on LeetCode example head = [1,2,3,4,5], k = 2 (expected [2,1,4,3,5]):

Roundgroup_prevkthgroup_nextlist after stitching
group 1dummy232 → 1 → 3 → 4 → 5 (new group_prev = 1)
group 21452 → 1 → 4 → 3 → 5 (new group_prev = 3)
check 33None (ran out)—fewer than k nodes left → return 2 → 1 → 4 → 3 → 5

Edge cases to test:

  • k = 1 → unchanged.
  • Length not a multiple of k: the last partial group stays as is.
  • k equal to the length → the whole list reversed.

Complexity: O(n) time (each node visited twice), O(1) space.

Medium 92. Reverse Linked List II Start here Google Amazon Microsoft Linked List

Problem: Reverse only positions left..right (1-indexed) of the list, in one pass.

Example 1
Input: head = [1,2,3,4,5], left = 2, right = 4
Output: [1,4,3,2,5]
Example 2
Input: head = [5], left = 1, right = 1
Output: [5]

Constraints: The number of nodes in the list is n. · 1 ≤ n ≤ 500 · -500 ≤ Node.val ≤ 500 · 1 ≤ left ≤ right ≤ n

Hint 1

Walk to the node just before position left.

Hint 2

Reverse exactly right − left + 1 nodes, then reconnect both ends of the reversed piece.

From brute force to the pattern: Copying the values between left and right into an array and writing them back reversed uses O(n) space. Reversing in place in a single pass, with a dummy node for left = 1, is O(n) time and O(1) space.

Spot it: "reverse from position left to right". Partial reversal: needs a dummy (left may be 1) and stitching. The second visualizer above traces it.

Key insight: "head insertion": the part's first node (tail) stays put while each following node is unhooked and moved to the front of the part. After right − left moves, the part is reversed and both ends are already connected.

Approach:

  1. Create a dummy before head and walk left − 1 steps to reach before.
  2. tail = before.next: the part's first node, which will end up last.
  3. Repeat right − left times: unhook moved = tail.next and insert it right after before.
  4. Return dummy.next.
class Solution:
    def reverseBetween(self, head: Optional[ListNode], left: int, right: int) -> Optional[ListNode]:
        # A dummy node in front of head: a node before the part always exists,
        # even when left == 1.
        dummy = ListNode(0, head)
        # Walk left - 1 steps so that 'before' stops just before position left.
        before = dummy
        for step in range(left - 1):
            before = before.next
        # tail = first node of the part. It stays where it is while the other
        # nodes are moved in front of it, so it ends up last.
        tail = before.next
        # Do right - left moves.
        for move in range(right - left):
            # The node right after tail is the one we move.
            moved = tail.next
            # Unhook it: tail skips over it.
            tail.next = moved.next
            # Insert it at the front of the part (right after 'before').
            moved.next = before.next
            before.next = moved
        # dummy.next is the real head (it changed if left was 1).
        return dummy.next

What the code is doing, line by line:

  1. dummy = ListNode(0, head): a fake node before the head, so the node before the part always exists (needed when left == 1).
  2. before = dummy, then for step in range(left - 1): before = before.next walks to the node just before position left.
  3. tail = before.next: the first node of the part. It stays put while the nodes after it are moved in front of it, so it ends up last.
  4. for move in range(right - left): a part of right - left + 1 nodes needs right - left moves.
  5. moved = tail.next picks the node right after tail; tail.next = moved.next unhooks it (tail skips over it).
  6. moved.next = before.next and before.next = moved insert it at the front of the part, right after before.
  7. return dummy.next: the real head of the list.

Dry run on LeetCode example head = [1,2,3,4,5], left = 2, right = 4 (expected [1,4,3,2,5]):

Stepbeforetailmovedlist (from dummy.next)
setup12—1 → 2 → 3 → 4 → 5
move 11231 → 3 → 2 → 4 → 5
move 21241 → 4 → 3 → 2 → 5
returndummy.next → 1 → 4 → 3 → 2 → 5

Edge cases to test:

  • left == right → unchanged.
  • left = 1 (the head changes).
  • right is the last node.

Complexity: O(n) time, O(1) space.

Medium 24. Swap Nodes in Pairs Google Meta Amazon Microsoft Linked List

Problem: Swap every two adjacent nodes (1→2→3→4 becomes 2→1→4→3) without changing values.

Example 1
Input: head = [1,2,3,4]
Output: [2,1,4,3]
Example 2
Input: head = []
Output: []

Constraints: The number of nodes in the list is in the range [0, 100]. · 0 ≤ Node.val ≤ 100

Hint 1

Swap nodes, not values: re-point three next pointers per pair.

Hint 2

Keep a pointer to the node before each pair (a dummy for the first pair), then advance two at a time.

From brute force to the pattern: Swapping the values is simple but is not what's asked (nodes must move). Re-linking each pair in place is O(n) time and O(1) space.

Spot it: it's k-group reversal with k = 2, small enough to write the three re-links by hand.

Key insight: with prev_node → first → second → rest, set first.next = rest, second.next = first, prev_node.next = second. Then first is the new prev_node.

Approach:

  1. dummy → head; prev_node = dummy.
  2. While two nodes first, second follow prev_node: first.next = second.next, second.next = first, prev_node.next = second.
  3. Move prev_node to first (now the second node of the pair).
  4. Return dummy.next.
class Solution:
    def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
        # The head changes (the 2nd node becomes the 1st), so use a dummy.
        dummy = ListNode(0, head)
        # prev_node = the node just BEFORE the pair we are about to swap.
        prev_node = dummy
        # A pair needs two nodes after prev_node.
        while prev_node.next is not None and prev_node.next.next is not None:
            # Name the two nodes of the pair.
            first = prev_node.next
            second = prev_node.next.next
            # first skips ahead to whatever comes after the pair.
            first.next = second.next
            # second points back to first.
            second.next = first
            # The node before the pair now points to second (the new front).
            prev_node.next = second
            # first is now the back of the pair, so it is the node
            # before the next pair.
            prev_node = first
        return dummy.next

What the code is doing, line by line:

  1. dummy = ListNode(0, head): the second node will become the head, so we use a fake node in front and return dummy.next at the end.
  2. prev_node = dummy: the node just before the pair we are about to swap.
  3. while prev_node.next is not None and prev_node.next.next is not None: only swap if two nodes follow prev_node. A single leftover node stays where it is.
  4. first = prev_node.next and second = prev_node.next.next give the two nodes of the pair names.
  5. first.next = second.next: first skips ahead to whatever comes after the pair.
  6. second.next = first: second now points back at first.
  7. prev_node.next = second: the node before the pair now points at second, the new front of the pair.
  8. prev_node = first: first is now the back of the pair, so it is the node before the next pair.

Dry run on LeetCode example head = [1,2,3,4] (expected [2,1,4,3]):

Pairprev_node (before pair)firstsecondlist after the swap
startdummy——1 → 2 → 3 → 4
1dummy122 → 1 → 3 → 4 (prev_node → 1)
21342 → 1 → 4 → 3 (prev_node → 3)
stop3fewer than 2 nodes after prev_node → return 2 → 1 → 4 → 3

Edge cases to test:

  • Empty list or a single node → unchanged.
  • Odd length: the last node stays put.
  • Exactly two nodes.

Complexity: O(n) time, O(1) space.

Medium 2130. Maximum Twin Sum of a Linked List Amazon Linked List

Problem: In a list of even length n, node i and node n−1−i are twins. Return the largest twin sum.

Example 1
Input: head = [5,4,2,1]
Output: 6
Explanation: Nodes 0 and 1 are the twins of nodes 3 and 2, respectively. All have twin sum = 6. There are no other nodes with twins in the linked list. Thus, the maximum twin sum of the linked list is 6.
Example 2
Input: head = [4,2,2,3]
Output: 7
Explanation: The nodes with twins present in this linked list are: - Node 0 is the twin of node 3 having a twin sum of 4 + 3 = 7. - Node 1 is the twin of node 2 having a twin sum of 2 + 2 = 4. Thus, the maximum twin sum of the linked list is max(7, 4) = 7.

Constraints: The number of nodes in the list is an even integer in the range [2, 10⁵]. · 1 ≤ Node.val ≤ 10⁵

Hint 1

Node i pairs with node n − 1 − i: the first half pairs with the second half read backwards.

Hint 2

Find the middle, reverse the second half, then walk both halves together summing pairs.

From brute force to the pattern: Copying values to an array and summing a[i] + a[n − 1 − i] is O(n) space. Middle + reverse + walk does it in place with O(1) space.

Spot it: you need to walk the first half forwards and the second half backwards at the same time. Reverse the second half in place.

Key insight: find the start of the second half with fast & slow, reverse from there, then walk both halves together.

Approach:

  1. Fast/slow puts slow on the first node of the second half.
  2. Reverse the second half starting at slow.
  3. Walk one pointer from head and one from the reversed half together, adding twin values.
  4. Return the maximum sum.
class Solution:
    def pairSum(self, head: Optional[ListNode]) -> int:
        # --- 1) Find the first node of the second half (fast & slow). ---
        slow = head
        fast = head
        while fast is not None and fast.next is not None:
            slow = slow.next          # slow moves 1 step
            fast = fast.next.next     # fast moves 2 steps
        # slow is now the first node of the second half.

        # --- 2) Reverse the second half, starting at slow. ---
        prev_node = None
        current = slow
        while current is not None:
            next_node = current.next      # save
            current.next = prev_node      # flip
            prev_node = current           # step
            current = next_node           # step
        # prev_node is the head of the reversed second half (the old last node).

        # --- 3) Walk both halves together and add the twins. ---
        best = 0
        front = head          # walks the first half forwards
        back = prev_node      # walks the second half "backwards"
        while back is not None:
            twin_sum = front.val + back.val
            # Keep the biggest sum seen so far.
            if twin_sum > best:
                best = twin_sum
            front = front.next
            back = back.next
        return best

What the code is doing, line by line:

  1. Phase 1: slow and fast both start at the head. Each round slow moves 1 node and fast moves 2. When fast can't move two more nodes, slow is on the first node of the second half (the list has even length).
  2. Phase 2: reverse the second half with save / flip / step / step, starting at slow. Afterwards prev_node is the old last node, which is now the head of the reversed second half.
  3. Phase 3: front walks the first half from the head; back walks the reversed second half. Node i meets its twin n − 1 − i at the same time.
  4. twin_sum = front.val + back.val, and the if twin_sum > best block keeps the largest sum seen so far.
  5. Both pointers step forward; the loop ends when back runs out. Return best.

Dry run on LeetCode example head = [5,4,2,1] (expected 6):

PhaseVariablesWhat happened
1 find middleslow = 5, fast = 5start
1 find middleslow = 4, fast = 2slow +1, fast +2
1 find middleslow = 2, fast = Noneslow +1, fast +2
1 find middlefast can't move 2 more → second half starts at 2
2 reverseflipped 2, next_node = 1reversed half: 2
2 reverseflipped 1, next_node = Nonereversed half: 1 → 2
3 comparefront = 5, back = 1twin_sum = 6, best = 6
3 comparefront = 4, back = 2twin_sum = 6, best = 6
returnback = Nonebest = 6

Edge cases to test:

  • Two nodes → their sum.
  • All equal values.
  • The maximum pair is in the middle.

Complexity: O(n) time, O(1) space.

Easy 206. Reverse Linked List (iterative + recursive) Start here Google Meta Amazon Cisco Microsoft Apple Linked List

Problem: Reverse the entire list and return the new head.

Example 1
Input: head = [1,2,3,4,5]
Output: [5,4,3,2,1]
Example 2
Input: head = [1,2]
Output: [2,1]

Constraints: The number of nodes in the list is the range [0, 5000]. · -5000 ≤ Node.val ≤ 5000

Hint 1

Each node's next must point backwards. What do you need to remember before changing it?

Hint 2

Keep prev and cur; save cur.next, point cur.next to prev, then step both forward.

From brute force to the pattern: Pushing the values on a stack and rebuilding uses O(n) extra space. Re-pointing every next in one pass is O(n) time and O(1) space (the recursive version uses O(n) stack).

Spot it: template A word for word. Interviewers often ask for the recursive version too.

Recursive insight: reverse everything after head; now head.next is the tail of that reversed part, so hang head after it: head.next.next = head, and cut head.next = None.

Approach:

  1. Iterative: prev_node = None, current = head; repeat save / flip / step / step until current is None; return prev_node.
  2. Recursive base case: an empty or single-node list is its own reverse.
  3. Reverse the rest; head.next is now the last node of that reversed part.
  4. Call the node after head second; set second.next = head (that is head.next.next = head) and head.next = None, then return the head found by the recursion.
class Solution:
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        # Iterative version: template A.
        prev_node = None          # nothing reversed yet
        current = head            # start flipping at the head
        while current is not None:
            next_node = current.next      # 1. save the rest of the list
            current.next = prev_node      # 2. flip the arrow backwards
            prev_node = current           # 3. step prev_node forward
            current = next_node           # 4. step current forward
        # The old tail is the new head.
        return prev_node

    def reverseListRecursive(self, head: Optional[ListNode]) -> Optional[ListNode]:
        # Base case: an empty list or a single node is already reversed.
        if head is None or head.next is None:
            return head
        # Reverse everything AFTER head. new_head is the old last node.
        new_head = self.reverseListRecursive(head.next)
        # head.next still points to the node that came right after head.
        # That node is now the TAIL of the reversed rest.
        second = head.next
        # Hang head after it: the node after me now points back to me.
        second.next = head
        # head is the new tail (for now), so it must point at nothing.
        head.next = None
        return new_head

What the code is doing, line by line:

  1. reverseList (iterative): prev_node = None (nothing reversed yet) and current = head.
  2. Inside while current is not None: do the four lines in order: save next_node = current.next, flip current.next = prev_node, step prev_node = current, step current = next_node.
  3. When current becomes None, prev_node is the old tail = new head, so return it.
  4. reverseListRecursive: base case: if the list is empty or has one node, it is already reversed, so return head.
  5. new_head = self.reverseListRecursive(head.next) trusts the recursion to reverse everything after head. new_head is the old last node and never changes on the way back up.
  6. second = head.next: this node was right after head, and after the recursive call it is the tail of the reversed rest.
  7. second.next = head hangs head after it; head.next = None makes head the new tail (otherwise we would get a 2-node cycle).
  8. Return new_head so every level passes the same new head upwards.

Dry run of reverseList on LeetCode example head = [1,2,3,4,5] (expected [5,4,3,2,1]):

Stepcurrent (flipped)next_node (saved)reversed part (from prev_node)rest (from current)
start——None1 → 2 → 3 → 4 → 5
11212 → 3 → 4 → 5
2232 → 13 → 4 → 5
3343 → 2 → 14 → 5
4454 → 3 → 2 → 15
55None5 → 4 → 3 → 2 → 1None
endNone—5 → 4 → 3 → 2 → 1None → loop stops, return prev_node = 5

Dry run of reverseListRecursive on head = [1,2,3]. The calls go down to depth 3 first; the rows show them finishing from the deepest call back up:

Call depthheadWhat happens when this call finisheslist from new_head
33base case: return 33
22second = 3; 3.next = 2; 2.next = None; return 33 → 2
11second = 2; 2.next = 1; 1.next = None; return 33 → 2 → 1

Edge cases to test:

  • Empty list → None.
  • A single node.
  • Two nodes.

Complexity: O(n) time; O(1) space iterative, O(n) stack recursive.

8. Common mistakes

Flipping before savingWriting current.next = prev_node before next_node = current.next loses the rest of the list forever.
Returning head or currentAfter the loop, current is None and head is the new tail. Return prev_node.
No dummy node for partial reversalIf left = 1, there's no "node before". A dummy removes that special case, so return dummy.next.
Forgetting to stitch the endsAfter reversing a part, connect the node before it to the new front and the new tail to the node after it. Otherwise you get a cut-off list or a cycle.
Reversing an incomplete last group (k-group)Check that k nodes exist before reversing.

9. Self-check quiz

Inside the reversal loop, which line must come first?

Save before you flip; otherwise the reference to the rest of the list is overwritten.

After while current is not None: finishes, which variable holds the new head?

current is None; prev_node is the last node processed, the old tail.

You're reversing positions 1..3. What makes the code free of special cases?

The dummy guarantees a "node before the part" even when the part starts at the head. Return dummy.next.

Check whether a linked list is a palindrome using O(1) extra space.

Find the middle with fast/slow, reverse the second half in place, then compare the two halves node by node.

10. Practice list (in order)

#ProblemLevelData structureTwist
206Reverse Linked ListEasyLinked ListWarm-up: template A, then recursive
21Merge Two Sorted ListsEasyLinked ListRe-link existing nodes behind a dummy; no new nodes
83Remove Duplicates from Sorted ListEasyLinked ListSkip the next node while it repeats the current value
234Palindrome Linked List (O(1) space)EasyLinked ListMiddle + reverse + compare
92Reverse Linked List IIMediumLinked ListDummy + head insertion
24Swap Nodes in PairsMediumLinked Listk-group with k = 2
2130Maximum Twin Sum of a Linked ListMediumLinked ListReverse the second half
143Reorder ListMediumLinked ListMiddle + reverse + weave
61Rotate ListMediumLinked ListClose into a ring, cut at n − k
445Add Two Numbers IIMediumLinked ListReverse both, add with carry, prepend each digit
2074Reverse Nodes in Even Length GroupsMediumLinked ListGroup sizes 1, 2, 3…; reverse only even ones
2Add Two NumbersMediumLinked ListWalk both lists at once with a carry; build the answer behind a dummy
82Remove Duplicates from Sorted List IIMediumLinked ListDelete the whole run of a repeated value; prev stays before it
138Copy List with Random PointerMediumLinked ListHash MapMap every old node to its copy, then wire next and random
2807Insert Greatest Common Divisors in Linked ListMediumLinked ListSplice a new node between every adjacent pair
86Partition ListMediumLinked ListTwo dummy-headed lists (smaller / rest), then link them
109Convert Sorted List to Binary Search TreeMediumLinked ListBSTIn-order simulation: build left subtree, consume the list node, build right subtree
147Insertion Sort ListMediumLinked ListSplice each node into a growing sorted list behind a dummy
237Delete Node in a Linked ListMediumLinked ListYou can't reach the previous node, so become the next node and delete it instead
328Odd Even Linked ListMediumLinked ListWeave two chains (odd positions, even positions), then attach evens after odds
707Design Linked ListMediumLinked ListA sentinel head plus a size counter keeps every operation simple
2058Find the Minimum and Maximum Number of Nodes Between Critical PointsMediumLinked ListWalk with prev/cur/next; track the first, the previous and the latest critical index
708Insert into a Sorted Circular Linked ListMediumLinked ListWalk once around: insert between prev ≤ x ≤ cur, or at the wrap point (max → min) if x is a new max or min
1721Swapping Nodes in a Linked ListMediumLinked ListWalk k steps for the first node; a second pointer started then reaches the k-th from the end
25Reverse Nodes in k-GroupHardLinked ListCheck k nodes, reverse with prev_node = group_next, stitch

Worked solutions for the practice list

Problems already solved in section 7 link back up there. Every other problem from the table is solved here.

Easy 234. Palindrome Linked List (O(1) space) Google Meta Nvidia Amazon Microsoft Linked List

Problem: Do the list's values read the same forwards and backwards, using only O(1) extra space?

Example 1
Input: head = [1,2,2,1]
Output: true
Example 2
Input: head = [1,2]
Output: false

Constraints: The number of nodes in the list is in the range [1, 10⁵]. · 0 ≤ Node.val ≤ 9

Hint 1

Copying the values into an array and comparing with its reverse uses O(n) space. How can you avoid that?

Hint 2

Find the middle, reverse the second half in place, then compare the two halves node by node.

From brute force to the pattern: An array copy or a stack gives O(n) space. Middle-finding plus an in-place reversal of the second half compares the halves with O(1) extra space (and you can reverse it back afterwards).

Spot it: Comparing the front with the back means reading the second half backwards. Reverse it in place instead of copying into an array.

Approach:

  1. Find the start of the second half with fast & slow.
  2. Reverse the second half with save / flip / step / step.
  3. Walk from head and from the reversed half together, comparing values.
  4. Any mismatch → False; otherwise True.
class Solution:
    def isPalindrome(self, head: Optional[ListNode]) -> bool:
        # --- 1) Move slow to the start of the second half (fast & slow). ---
        slow = head
        fast = head
        while fast is not None and fast.next is not None:
            slow = slow.next          # 1 step
            fast = fast.next.next     # 2 steps

        # --- 2) Reverse the second half in place. ---
        prev_node = None
        current = slow
        while current is not None:
            next_node = current.next      # save
            current.next = prev_node      # flip
            prev_node = current           # step
            current = next_node           # step

        # --- 3) Walk both halves together and compare values. ---
        front = head          # start of the first half
        back = prev_node      # start of the reversed second half
        while back is not None:
            if front.val != back.val:
                # One mismatch is enough: not a palindrome.
                return False
            front = front.next
            back = back.next
        # Every pair matched.
        return True

What the code is doing, line by line:

  1. Phase 1: slow moves 1 node per round and fast moves 2. When fast can't continue, slow is at the start of the second half (for odd length it is the middle node, which is fine: it compares with itself).
  2. Phase 2: reverse the second half in place with save / flip / step / step, starting at slow. Afterwards prev_node is the head of the reversed second half.
  3. Phase 3: front starts at the head and back at prev_node. While back still has nodes, compare the values.
  4. if front.val != back.val: return False: one mismatch is enough to prove it is not a palindrome.
  5. Otherwise step both pointers. If the loop ends without a mismatch, every pair matched, so return True.

Dry run on LeetCode example head = [1,2,2,1] (expected True). Two nodes have value 2, so the node position is shown too:

PhaseVariablesWhat happened
1 find middleslow = 1 (node #1), fast = 1 (node #1)start
1 find middleslow = 2 (node #2), fast = 2 (node #3)slow +1, fast +2
1 find middleslow = 2 (node #3), fast = Noneslow +1, fast +2
1 find middlesecond half starts at slow = 2 (node #3)
2 reverseflipped 2, next_node = 1reversed half: 2
2 reverseflipped 1, next_node = Nonereversed half: 1 → 2
2 reversefirst half (from head): 1 → 2 → 2
3 comparefront = 1 (node #1), back = 1 (node #4)equal
3 comparefront = 2 (node #2), back = 2 (node #3)equal
returnback = Noneevery pair matched → return True

Edge cases to test:

  • A single node → True.
  • Even and odd lengths.
  • Two nodes with different values → False.

Complexity: O(n) time, O(1) space.

Medium 143. Reorder List Google Meta Amazon Microsoft Linked List

Problem: Rearrange L0 → L1 → … → Ln into L0 → Ln → L1 → Ln−1 → … by changing links only.

Example 1
Input: head = [1,2,3,4]
Output: [1,4,2,3]
Example 2
Input: head = [1,2,3,4,5]
Output: [1,5,2,4,3]

Constraints: The number of nodes in the list is in the range [1, 5 * 10⁴]. · 1 ≤ Node.val ≤ 1000

Hint 1

The result interleaves the first half with the reversed second half.

Hint 2

Three steps: find the middle (fast/slow), reverse the second half, then weave the two halves together.

From brute force to the pattern: Copying the nodes into an array and re-linking with two indices is O(n) time but O(n) space. Middle + reverse + merge does it in place in O(n) time and O(1) space.

Spot it: You need the back half in reverse order → split at the middle, reverse the second half, then weave.

Approach:

  1. Fast/slow with while fast.next and fast.next.next puts slow at the end of the first half.
  2. Cut after slow and reverse the second half.
  3. Weave: front.next = back, back.next = front_next, then advance both.
  4. Stop when the (shorter or equal) second half runs out.
class Solution:
    def reorderList(self, head: Optional[ListNode]) -> None:
        # 0 or 1 node: nothing to reorder.
        if head is None or head.next is None:
            return

        # --- 1) Move slow to the END of the first half. ---
        slow = head
        fast = head
        while fast.next is not None and fast.next.next is not None:
            slow = slow.next
            fast = fast.next.next

        # --- 2) Cut the list in two and reverse the second half. ---
        prev_node = None
        current = slow.next       # the second half starts after slow
        slow.next = None          # cut: the first half now ends at slow
        while current is not None:
            next_node = current.next      # save
            current.next = prev_node      # flip
            prev_node = current           # step
            current = next_node           # step

        # --- 3) Weave the halves: front1, back1, front2, back2, ... ---
        front = head
        back = prev_node
        while back is not None:
            # Remember where each half continues before we change links.
            front_next = front.next
            back_next = back.next
            # front -> back -> (old front.next)
            front.next = back
            back.next = front_next
            # Move both pointers to their saved next nodes.
            front = front_next
            back = back_next

What the code is doing, line by line:

  1. if head is None or head.next is None: return: 0 or 1 nodes, nothing to reorder.
  2. Phase 1: with the condition fast.next is not None and fast.next.next is not None, slow stops at the end of the first half (the first half gets the extra node when the length is odd).
  3. Phase 2: current = slow.next is the start of the second half; slow.next = None cuts the list into two separate lists. Then save / flip / step / step reverses the second half, and prev_node is its new head.
  4. Phase 3 (weave): front walks the first half and back the reversed second half. Before changing links, save front_next and back_next.
  5. front.next = back and back.next = front_next put one back-node between two front-nodes.
  6. Move front and back to their saved next nodes. The loop ends when the second half (never longer than the first) runs out.

Dry run on LeetCode example head = [1,2,3,4,5] (expected [1,5,2,4,3]):

PhaseVariablesList state
1 find middleslow = 1, fast = 1start
1 find middleslow = 2, fast = 3slow +1, fast +2
1 find middleslow = 3, fast = 5slow +1, fast +2
2 cutslow = 3first half: 1 → 2 → 3 | second half: 4 → 5
2 reverseflipped 4reversed second half: 4
2 reverseflipped 5reversed second half: 5 → 4
3 weavefront = 1, back = 5 → next front = 2, next back = 4list: 1 → 5 → 2 → 3
3 weavefront = 2, back = 4 → next front = 3, next back = Nonelist: 1 → 5 → 2 → 4 → 3

Edge cases to test:

  • One or two nodes → unchanged.
  • Odd length: the middle node ends up last.
  • Forgetting to cut the first half off (set its tail's next to None) creates a cycle.

Complexity: O(n) time, O(1) space.

Medium 61. Rotate List Google Meta Amazon Microsoft Linked List

Problem: Rotate the list to the right by k places: the last k nodes move to the front.

Example 1
Input: head = [1,2,3,4,5], k = 2
Output: [4,5,1,2,3]
Example 2
Input: head = [0,1,2], k = 4
Output: [2,0,1]

Constraints: The number of nodes in the list is in the range [0, 500]. · -100 ≤ Node.val ≤ 100 · 0 ≤ k ≤ 2 * 10⁹

Hint 1

Rotating by the list's length changes nothing, so use k % length.

Hint 2

Make the list circular, then break it length − k nodes after the old head.

From brute force to the pattern: Rotating one step at a time is O(n · k). Measuring the length once, closing the ring and cutting in the right place is O(n).

Spot it: Re-linking pointers in place, like reversal. The trick is to close the list into a ring and cut it at the right spot.

Approach:

  1. Measure the length and find the tail.
  2. shift = k % length; if it's 0, return head unchanged.
  3. Link tail.next = head to make a ring.
  4. Walk length − shift − 1 steps from head to the new tail; the new head is new_tail.next; then cut the ring.
class Solution:
    def rotateRight(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
        # 0 or 1 node: rotating changes nothing.
        if head is None or head.next is None:
            return head

        # --- 1) Measure the length and find the tail. ---
        length = 1
        tail = head
        while tail.next is not None:
            tail = tail.next
            length = length + 1

        # --- 2) Rotating by 'length' places changes nothing, so drop full turns. ---
        # shift = how many places we really need to rotate.
        shift = k % length
        if shift == 0:
            return head

        # --- 3) Close the list into a ring. ---
        tail.next = head

        # --- 4) The new tail is length - shift - 1 steps from the head. ---
        new_tail = head
        for step in range(length - shift - 1):
            new_tail = new_tail.next
        # The node after the new tail is the new head.
        new_head = new_tail.next
        # Cut the ring open.
        new_tail.next = None
        return new_head

What the code is doing, line by line:

  1. if head is None or head.next is None: return head: 0 or 1 nodes can't change by rotating.
  2. length = 1, tail = head, then walk tail to the last node, adding 1 to length per step.
  3. shift = k % length: rotating by length places gives the same list back, so only the remainder matters. If shift is 0, return head unchanged.
  4. tail.next = head closes the list into a ring.
  5. The last shift nodes must move to the front, so the new tail is the node at position length − shift, which is length − shift − 1 steps from the head. The for step in range(length - shift - 1) loop walks there.
  6. new_head = new_tail.next, then new_tail.next = None cuts the ring open. Return new_head.

Dry run on LeetCode example head = [1,2,3,4,5], k = 2 (expected [4,5,1,2,3]):

PhaseVariablesWhat happened
measurelength = 1, tail = 1start at head
measurelength = 2, tail = 2walk one node
measurelength = 3, tail = 3walk one node
measurelength = 4, tail = 4walk one node
measurelength = 5, tail = 5walk one node
shiftshift = k % length = 2 % 5 = 2shift is not 0, keep going
ringtail.next = head5 now points to 1 (the list is a circle)
walknew_tail = 1need length - shift - 1 = 2 steps
walknew_tail = 2step 1
walknew_tail = 3step 2
cutnew_head = 4, 3.next = Noneresult: 4 → 5 → 1 → 2 → 3

Edge cases to test:

  • k = 0 or k a multiple of the length → unchanged.
  • Empty list or a single node.
  • Very large k (e.g. 2·10⁹): you must take % length.

Complexity: O(n) time, O(1) space.

Medium 445. Add Two Numbers II Google Linked List

Problem: Two non-negative numbers are stored as lists with the most-significant digit first. Return their sum as a list in the same order.

Example 1
Input: l1 = [7,2,4,3], l2 = [5,6,4]
Output: [7,8,0,7]
Example 2
Input: l1 = [2,4,3], l2 = [5,6,4]
Output: [8,0,7]

Constraints: The number of nodes in each linked list is in the range [1, 100]. · 0 ≤ Node.val ≤ 9 · It is guaranteed that the list represents a number that does not have leading zeros.

Hint 1

The most significant digit comes first, but addition starts from the least significant one.

Hint 2

Reverse both lists (or push the digits onto stacks), add with a carry, and build the result from the front.

From brute force to the pattern: Converting to integers works in Python but defeats the purpose and fails in languages with fixed-size integers. Reversing the lists, adding column by column with a carry, and reversing back is O(m + n).

Spot it: Addition starts from the least-significant digit, which is at the end. Reverse both lists in place first.

Key insight: Build the answer by prepending each new digit node. It comes out most-significant first, so no final reversal is needed.

Approach:

  1. Reverse both input lists (template A).
  2. Add digit by digit with a carry: digit = total % 10 and carry = total // 10.
  3. Prepend a node for digit to the result.
  4. Continue while either list or the carry remains.
class Solution:
    def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
        # --- 1) Reverse both lists so the ones digit comes first. ---
        first = self.reverse_list(l1)
        second = self.reverse_list(l2)

        # --- 2) Add digit by digit, like on paper, keeping a carry. ---
        carry = 0
        result = None             # the answer list, built from the back
        while first is not None or second is not None or carry != 0:
            total = carry
            # Add the digit from the first number, if it still has digits.
            if first is not None:
                total = total + first.val
                first = first.next
            # Add the digit from the second number, if it still has digits.
            if second is not None:
                total = total + second.val
                second = second.next
            digit = total % 10        # the digit we write down
            carry = total // 10       # the 1 we carry to the next column
            # PREPEND the new digit: put it in front of the answer so far.
            # The answer then comes out most-significant digit first.
            new_node = ListNode(digit)
            new_node.next = result
            result = new_node
        return result

    def reverse_list(self, head):
        # Template A: save, flip, step, step.
        prev_node = None
        current = head
        while current is not None:
            next_node = current.next
            current.next = prev_node
            prev_node = current
            current = next_node
        return prev_node

What the code is doing, line by line:

  1. first = self.reverse_list(l1) and second = self.reverse_list(l2): the helper method at the bottom is template A. After reversing, each list starts with its ones digit, which is where adding starts.
  2. carry = 0 and result = None: no carry yet and an empty answer list.
  3. while first is not None or second is not None or carry != 0: keep going while either number still has digits or a carry is left over (e.g. 99 + 1 needs an extra digit).
  4. total = carry, then each if block adds that number's digit (only if it still has one) and moves its pointer forward. This replaces the compact a.val if a else 0 trick.
  5. digit = total % 10 is the digit we write down; carry = total // 10 is the 1 (or 0) carried to the next column.
  6. new_node.next = result then result = new_node prepends the digit. We produce digits ones-first, and prepending each one puts the answer most-significant-first, as required.
  7. Return result.

Dry run on LeetCode example l1 = [7,2,4,3], l2 = [5,6,4] (7243 + 564 = 7807, expected [7,8,0,7]):

Stepdigit from firstdigit from secondcarry intotaldigit / carry outresult (after prepend)
reverse3 → 4 → 2 → 74 → 6 → 5———None
column 134077 / 07
column 2460100 / 10 → 7
column 325188 / 08 → 0 → 7
column 47—077 / 07 → 8 → 0 → 7

Edge cases to test:

  • Different lengths, e.g. [7, 2, 4, 3] + [5, 6, 4].
  • A final carry adds a new leading digit, e.g. [5] + [5] → [1, 0].
  • One number is 0.

Complexity: O(m + n) time, O(1) extra space besides the output.

Medium 2074. Reverse Nodes in Even Length Groups Google Linked List

Problem: Split the list into consecutive groups of sizes 1, 2, 3, … (the last group may be shorter). Reverse every group whose actual length is even.

Example 1
Input: head = [5,2,6,3,9,1,7,3,8,4]
Output: [5,6,2,3,9,1,4,8,3,7]
Explanation: - The length of the first group is 1, which is odd, hence no reversal occurs. - The length of the second group is 2, which is even, hence the nodes are reversed. - The length of the third group is 3, which is odd, hence no reversal occurs. - The length of the last group is 4, which is even, hence the nodes are reversed.
Example 2
Input: head = [1,1,0,6]
Output: [1,0,1,6]
Explanation: - The length of the first group is 1. No reversal occurs. - The length of the second group is 2. The nodes are reversed. - The length of the last group is 1. No reversal occurs.

Constraints: The number of nodes in the list is in the range [1, 10⁵]. · 0 ≤ Node.val ≤ 10⁵

Hint 1

Groups have lengths 1, 2, 3, … except the last group, which may be shorter. Measure each group's real length first.

Hint 2

Reverse a group only if its actual length is even, then reconnect it like in Reverse Nodes in k-Group.

From brute force to the pattern: Copying values into an array, reversing even-length chunks and writing them back uses O(n) space. Walking group by group and reversing in place is O(n) time and O(1) space.

Spot it: Reversing chunks in place → k-group reversal where k grows each time and a parity check decides whether to reverse.

Approach:

  1. group_prev = head (group 1 has size 1 and is never reversed); group_size = 2.
  2. Count up to group_size nodes after group_prev to get the group's real length.
  3. If it's even, reverse those nodes with prev_node starting at group_next (the node after the group), set group_prev.next = prev_node, and move group_prev to group_first (the old first node).
  4. If it's odd, move group_prev to group_last (the group's last node).
  5. group_size = group_size + 1; repeat until no nodes remain.
class Solution:
    def reverseEvenLengthGroups(self, head: Optional[ListNode]) -> Optional[ListNode]:
        # Group 1 is just the head (size 1, odd), so it is never reversed.
        # group_prev = the node just BEFORE the current group.
        group_prev = head
        group_size = 2            # the size the next group SHOULD have
        while group_prev.next is not None:
            # --- 1) Count the real length of this group (the last may be shorter). ---
            group_last = group_prev
            count = 0
            while count < group_size and group_last.next is not None:
                group_last = group_last.next
                count = count + 1

            if count % 2 == 0:
                # --- 2a) Even length: reverse the group (like k-group). ---
                group_first = group_prev.next
                group_next = group_last.next
                # Start prev_node at group_next so the old first node
                # ends up pointing at the rest of the list.
                prev_node = group_next
                current = group_first
                while current is not group_next:
                    next_node = current.next      # save
                    current.next = prev_node      # flip
                    prev_node = current           # step
                    current = next_node           # step
                # Stitch the front: prev_node is the old last node = new first node.
                group_prev.next = prev_node
                # The old first node is now the group's tail.
                group_prev = group_first
            else:
                # --- 2b) Odd length: leave it alone, just jump over it. ---
                group_prev = group_last

            # The next group should be one node longer.
            group_size = group_size + 1
        return head

What the code is doing, line by line:

  1. group_prev = head: group 1 is just the head. Its size is 1 (odd), so it is never reversed, and the head never changes. That's why no dummy is needed.
  2. group_size = 2: the size the next group should have. It grows by 1 after every group.
  3. while group_prev.next is not None: continue while there are nodes after the last finished group.
  4. Count the real length: walk group_last forward up to group_size nodes, counting them in count. The last group may be shorter, which is why we use count (not group_size) to decide.
  5. Even count: name group_first and group_next, then reverse with save / flip / step / step, starting prev_node at group_next (same trick as problem 25) so the old first node points at the rest of the list.
  6. group_prev.next = prev_node stitches the front (prev_node is the old last node = new first). group_prev = group_first, because the old first node is now the group's tail.
  7. Odd count: leave the group alone and jump over it: group_prev = group_last.
  8. group_size = group_size + 1, then do the next group. Return head at the end.

Dry run on LeetCode example head = [5,2,6,3,9,1,7,3,8,4] (expected [5,6,2,3,9,1,4,8,3,7]):

Groupgroup_sizecount (real)nodes in groupActionlist after this group
group 1115odd (head, never reversed)5 → 2 → 6 → 3 → 9 → 1 → 7 → 3 → 8 → 4
group 2222, 6even → reversed5 → 6 → 2 → 3 → 9 → 1 → 7 → 3 → 8 → 4
group 3333, 9, 1odd → skipped5 → 6 → 2 → 3 → 9 → 1 → 7 → 3 → 8 → 4
group 4447, 3, 8, 4even → reversed5 → 6 → 2 → 3 → 9 → 1 → 4 → 8 → 3 → 7

Edge cases to test:

  • The last group is shorter and its length is even → reverse it.
  • The last group is shorter and odd → leave it.
  • A list of 1 or 2 nodes.

Complexity: O(n) time, O(1) space.

Easy 21. Merge Two Sorted Lists Start here Google Meta Amazon Microsoft Linked List

Problem: Merge two sorted linked lists into one sorted list by splicing their nodes together. Return the new head.

Example 1
Input: list1 = [1,2,4], list2 = [1,3,4]
Output: [1,1,2,3,4,4]
Example 2
Input: list1 = [], list2 = []
Output: []

Constraints: The number of nodes in both lists is in the range [0, 50]. · -100 ≤ Node.val ≤ 100 · Both list1 and list2 are sorted in non-decreasing order.

Hint 1

Compare the two front nodes. Which goes first?

Hint 2

Attach the smaller front node to a tail pointer behind a dummy node; at the end, attach whatever is left.

From brute force to the pattern: Copying all values into an array, sorting and rebuilding is O((m + n) log(m + n)) and allocates new nodes. Merging by re-linking the existing nodes is O(m + n) time and O(1) extra space.

Spot it: Two sorted sequences → compare the two fronts and take the smaller (the merge step of merge sort). With linked lists we re-point next pointers instead of copying values.

Approach:

  1. Create a dummy node and a tail pointer.
  2. While both lists have nodes: attach the smaller front node to tail, advance that list, and move tail.
  3. One list may have nodes left. They are already sorted, so attach the rest in one step.
  4. Return dummy.next.
class Solution:
    def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
        dummy = ListNode()   # placeholder in front of the merged list
        tail = dummy
        while list1 is not None and list2 is not None:
            if list1.val <= list2.val:
                tail.next = list1      # reuse list1's node
                list1 = list1.next
            else:
                tail.next = list2
                list2 = list2.next
            tail = tail.next
        # at most one list still has nodes, and they are already sorted
        if list1 is not None:
            tail.next = list1
        else:
            tail.next = list2
        return dummy.next

What the code is doing, line by line:

  1. dummy, tail: tail is always the last node of the merged list so far.
  2. while list1 is not None and list2 is not None: compare only while both have a front node.
  3. if list1.val <= list2.val: take the smaller front. <= keeps equal values in their original order (a stable merge).
  4. tail.next = list1 re-uses the existing node; list1 = list1.next moves that list forward; tail = tail.next moves to the new end.
  5. After the loop, attach whichever list is left. It's already sorted and bigger than everything placed so far.

Dry run on the example:

list1 = [1, 2, 4], list2 = [1, 3, 4] (LeetCode Example 1). Returns [1, 1, 2, 3, 4, 4].

list1 frontlist2 fronttakemerged so far
11list1's 1 (tie → list1)1
21list2's 11 1
23list1's 21 1 2
43list2's 31 1 2 3
44list1's 4 (tie)1 1 2 3 4
None4list1 is empty → attach the rest of list21 1 2 3 4 4

Edge cases to test:

  • One or both lists empty.
  • Equal values in both lists.
  • Every value of one list smaller than every value of the other.

Complexity: O(m + n) time, O(1) extra space (nodes are re-linked, not copied).

Easy 83. Remove Duplicates from Sorted List Google Meta Amazon Microsoft Linked List

Problem: The linked list is sorted. Delete duplicates so each value appears only once, and return the head.

Example 1
Input: head = [1,1,2]
Output: [1,2]
Example 2
Input: head = [1,1,2,3,3]
Output: [1,2,3]

Constraints: The number of nodes in the list is in the range [0, 300]. · -100 ≤ Node.val ≤ 100 · The list is guaranteed to be sorted in ascending order.

Hint 1

The list is sorted, so duplicates are next to each other.

Hint 2

If cur.next has the same value as cur, skip it by re-pointing cur.next; otherwise move on.

From brute force to the pattern: A set of seen values plus rebuilding the list uses O(n) space. Because the list is sorted, comparing each node with its neighbour removes duplicates in place: O(n) time, O(1) space.

Spot it: Sorted means equal values sit next to each other. Stay on a node and unlink its next for as long as it has the same value. That's in-place pointer surgery with one pointer.

Approach:

  1. cur = head.
  2. While cur and cur.next exist: if cur.next.val == cur.val, unlink cur.next (cur.next = cur.next.next) and stay on cur.
  3. Otherwise move cur forward.
  4. Return head (the first node is never removed).
class Solution:
    def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
        cur = head
        while cur is not None and cur.next is not None:
            if cur.next.val == cur.val:
                # same value again: unlink the next node, but stay here
                # (the new next might be another copy)
                cur.next = cur.next.next
            else:
                # a new value: move on to it
                cur = cur.next
        return head

What the code is doing, line by line:

  1. cur = head: the node whose duplicates we are removing.
  2. while cur is not None and cur.next is not None: we look one node ahead, so both must exist.
  3. if cur.next.val == cur.val: cur.next = cur.next.next: drop the copy. We do not move cur, because a run like 1 → 1 → 1 needs several removals.
  4. else: cur = cur.next: the next value is different, so it becomes the new current node.
  5. return head: the head is the first of its value, so it always stays.

Dry run on the example:

head = [1, 1, 2, 3, 3] (LeetCode Example 2). Returns [1, 2, 3].

curcur.nextequal?actionlist now
11yesunlink1 → 2 → 3 → 3
12nomove on1 → 2 → 3 → 3
23nomove on1 → 2 → 3 → 3
33yesunlink1 → 2 → 3
3None—stop1 → 2 → 3

Edge cases to test:

  • An empty list.
  • All values equal → one node remains.
  • Duplicates at the end of the list.

Complexity: O(n) time, O(1) space.

Medium 2. Add Two Numbers Google Meta Amazon Cisco Microsoft Linked List

Problem: Two non-empty linked lists store non-negative integers with their digits in reverse order (2 → 4 → 3 is 342). Return their sum as a linked list in the same reverse format.

Example 1
Input: l1 = [2,4,3], l2 = [5,6,4]
Output: [7,0,8]
Explanation: 342 + 465 = 807.
Example 2
Input: l1 = [0], l2 = [0]
Output: [0]

Constraints: The number of nodes in each linked list is in the range [1, 100]. · 0 ≤ Node.val ≤ 9 · It is guaranteed that the list represents a number that does not have leading zeros.

Hint 1

The digits are stored least significant first, which is exactly the order you add them in.

Hint 2

Walk both lists at the same time, adding digit + digit + carry; keep going while either list or the carry remains.

From brute force to the pattern: Converting both lists to integers and back works in Python but would overflow in most languages and misses the point. Column addition with a carry, one node at a time, is O(max(m, n)).

Spot it: Reverse order means the head is the ones digit, exactly where column addition starts. So walk both lists together, add digit + digit + carry, and append one new node per column.

Approach:

  1. Make a dummy node and a tail pointer to append to; carry = 0.
  2. While either list has nodes left or carry is not zero: add the two current digits (0 for a list that has ended) and carry.
  3. The new digit is total % 10; the new carry is total // 10. Append the digit and move tail.
  4. Advance whichever input lists still have nodes.
  5. Return dummy.next.
class Solution:
    def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
        dummy = ListNode()   # the answer is built after this placeholder
        tail = dummy
        carry = 0
        # a final carry (e.g. 5 + 5 = 10) still needs its own node
        while l1 is not None or l2 is not None or carry != 0:
            total = carry
            if l1 is not None:
                total = total + l1.val
                l1 = l1.next
            if l2 is not None:
                total = total + l2.val
                l2 = l2.next
            # write the ones digit, carry the tens digit to the next column
            tail.next = ListNode(total % 10)
            tail = tail.next
            carry = total // 10
        return dummy.next

What the code is doing, line by line:

  1. dummy, tail: appending after a placeholder means the first digit needs no special case.
  2. while l1 is not None or l2 is not None or carry != 0: keep going while any digit or carry remains. The lists can have different lengths.
  3. total = carry, then add l1.val and l2.val only if that list still has a node, stepping it forward as we use it.
  4. ListNode(total % 10): the digit for this column (e.g. 10 → 0). carry = total // 10 is 0 or 1 for the next column.
  5. return dummy.next: skip the placeholder.

Dry run on the example:

l1 = [2, 4, 3] (342), l2 = [5, 6, 4] (465) (LeetCode Example 1). Returns [7, 0, 8] (807).

columnl1 digitl2 digitcarry intotalnew nodecarry out
ones250770
tens4601001
hundreds341880

Edge cases to test:

  • Different lengths.
  • A carry at the very end, e.g. [9, 9] + [1] → [0, 0, 1].
  • Both numbers are 0.

Complexity: O(max(m, n)) time, O(max(m, n)) for the output list.

Medium 82. Remove Duplicates from Sorted List II Google Meta Amazon Linked List

Problem: The linked list is sorted. Delete every node whose value appears more than once, leaving only values that were distinct. Return the head.

Example 1
Input: head = [1,2,3,3,4,4,5]
Output: [1,2,5]
Example 2
Input: head = [1,1,1,2,3]
Output: [2,3]

Constraints: The number of nodes in the list is in the range [0, 300]. · -100 ≤ Node.val ≤ 100 · The list is guaranteed to be sorted in ascending order.

Hint 1

Every copy of a repeated value must go, including the first one, and the head might be one of them.

Hint 2

Use a dummy node and a prev pointer on the last node you're sure to keep; skip whole runs of equal values.

From brute force to the pattern: Counting each value's occurrences and rebuilding the list uses O(n) space. Since the list is sorted, duplicates form runs that can be skipped in place: O(n) time, O(1) space.

Spot it: Unlike LeetCode 83, the first copy must go too, and that may be the head. A dummy node plus a prev pointer that always sits on the last node we are sure to keep makes this a clean in-place splice.

Approach:

  1. dummy → head; prev = dummy (the last kept node), cur = head.
  2. If cur starts a run of equal values (cur.next has the same value), walk cur to the last node of the run, then splice the whole run out: prev.next = cur.next.
  3. Otherwise cur is unique: keep it by moving prev onto it.
  4. Move cur forward and repeat. Return dummy.next.
class Solution:
    def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
        dummy = ListNode(0, head)   # the head itself may be deleted
        prev = dummy                # last node we are sure to keep
        cur = head
        while cur is not None:
            if cur.next is not None and cur.next.val == cur.val:
                # cur starts a run of duplicates: walk to the run's last node
                while cur.next is not None and cur.next.val == cur.val:
                    cur = cur.next
                # cut the whole run out; prev stays where it is
                prev.next = cur.next
            else:
                # cur is unique: it stays, so prev moves onto it
                prev = cur
            cur = cur.next
        return dummy.next

What the code is doing, line by line:

  1. dummy = ListNode(0, head): lets us delete the head with the same code as any other node.
  2. prev: the last node that is definitely in the answer. We only ever splice after it.
  3. if cur.next is not None and cur.next.val == cur.val: cur is the first node of a duplicate run.
  4. Inner while: move cur to the last copy of that value.
  5. prev.next = cur.next: one assignment removes the entire run. prev does not move: the next node might start another run.
  6. else: prev = cur: a value with no copies is kept.
  7. cur = cur.next: continue after the run (or after the kept node).

Dry run on the example:

head = [1, 2, 3, 3, 4, 4, 5] (LeetCode Example 1). Returns [1, 2, 5].

currun of duplicates?actionprev afterlist now
1nokeep 111 2 3 3 4 4 5
2nokeep 221 2 3 3 4 4 5
3yes (3, 3)2.next = 4, the 3s are gone21 2 4 4 5
4yes (4, 4)2.next = 5, the 4s are gone21 2 5
5nokeep 551 2 5

Edge cases to test:

  • All values duplicated → empty list.
  • The head is part of a duplicate run.
  • No duplicates → unchanged.

Complexity: O(n) time, O(1) space.

Medium 138. Copy List with Random Pointer Google Meta Nvidia Amazon Microsoft Linked ListHash Map

Problem: Each node has a next pointer and a random pointer that may point to any node or None. Return a deep copy: brand-new nodes whose pointers mirror the original structure.

Example 1
Input: head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
Output: [[7,null],[13,0],[11,4],[10,2],[1,0]]
Example 2
Input: head = [[1,1],[2,1]]
Output: [[1,1],[2,1]]

Constraints: 0 ≤ n ≤ 1000 · -10⁴ ≤ Node.val ≤ 10⁴ · Node.random is null or is pointing to some node in the linked list.

Hint 1

random can point to a node whose copy doesn't exist yet.

Hint 2

Create every copy first (map old node → new node), then wire up next and random in a second pass.

From brute force to the pattern: Searching the list to find where each random points is O(n²). An old → new hash map makes every lookup O(1): two passes, O(n). (Weaving the copies between the originals gets the space down to O(1).)

Spot it: The trouble is random: it may point to a node whose copy doesn't exist yet. A hash map from old node → new node solves it. Create all copies first, then connect them.

Key insight: Two passes. Pass 1 creates a copy of every node. Pass 2 sets copy.next = old_to_new[old.next] and copy.random = old_to_new[old.random]. Mapping None → None avoids special cases. (An O(1)-space variant weaves copies between the originals, but the map version is far easier to get right.)

Approach:

  1. Start the map with {None: None}, so a missing pointer copies to a missing pointer.
  2. Pass 1: walk the list and create Node(old.val) for every node, storing old → copy.
  3. Pass 2: walk again and set each copy's next and random by looking up the copies of the old targets.
  4. Return the copy of head.
class Solution:
    def copyRandomList(self, head: "Optional[Node]") -> "Optional[Node]":
        # old node -> its copy; None maps to None so empty pointers copy for free
        old_to_new = {None: None}
        # pass 1: make a copy of every node (pointers not set yet)
        cur = head
        while cur is not None:
            old_to_new[cur] = Node(cur.val)
            cur = cur.next
        # pass 2: wire up next and random using the copies
        cur = head
        while cur is not None:
            copy = old_to_new[cur]
            copy.next = old_to_new[cur.next]
            copy.random = old_to_new[cur.random]
            cur = cur.next
        return old_to_new[head]

What the code is doing, line by line:

  1. old_to_new = {None: None}: nodes can be dictionary keys (by identity). Seeding None means old_to_new[cur.random] works even when random is empty.
  2. Pass 1, old_to_new[cur] = Node(cur.val): every copy exists before we connect anything, so pass 2 can point to any of them.
  3. Pass 2, copy.next = old_to_new[cur.next]: the copy's next is the copy of the old next, never the old node itself.
  4. copy.random = old_to_new[cur.random]: same idea for the random pointer.
  5. return old_to_new[head]: the copied head (or None for an empty list).

Dry run on the example:

head = [[7, null], [13, 0], [11, 4], [10, 2], [1, 0]] (LeetCode Example 1; each pair is [value, index that random points to]). Name the originals A..E and the copies A'..E'.

old nodevalold.nextold.randomcopy gets next / random
A7BNoneA'.next = B', A'.random = None
B13CAB'.next = C', B'.random = A'
C11DEC'.next = D', C'.random = E'
D10ECD'.next = E', D'.random = C'
E1NoneAE'.next = None, E'.random = A'

Pass 1 already created A'..E', so every lookup in pass 2 succeeds. The copy has the same shape, [[7, null], [13, 0], [11, 4], [10, 2], [1, 0]], with no shared nodes.

Edge cases to test:

  • An empty list.
  • random pointing to the node itself.
  • random is None everywhere.

Complexity: O(n) time, O(n) space for the map.

Medium 2807. Insert Greatest Common Divisors in Linked List Linked List

Problem: Between every pair of adjacent nodes, insert a new node whose value is the greatest common divisor (GCD) of the two values. Return the head.

Example 1
Input: head = [18,6,10,3]
Output: [18,6,6,2,10,1,3]
Explanation: The 1st diagram denotes the initial linked list and the 2nd diagram denotes the linked list after inserting the new nodes (nodes in blue are the inserted nodes). - We insert the greatest common divisor of 18 and 6 = 6 between the 1st and the 2nd nodes. - We insert the greatest common divisor of 6 and 10 = 2 between the 2nd and the 3rd nodes. - We insert the greatest common divisor of 10 and 3 = 1 between the 3rd and the 4th nodes. There are no more adjacent nodes, so we return the linked list.
Example 2
Input: head = [7]
Output: [7]
Explanation: The 1st diagram denotes the initial linked list and the 2nd diagram denotes the linked list after inserting the new nodes. There are no pairs of adjacent nodes, so we return the initial linked list.

Constraints: The number of nodes in the list is in the range [1, 5000]. · 1 ≤ Node.val ≤ 1000

Hint 1

For every adjacent pair, a new node goes between them.

Hint 2

Create the node already pointing at the next original node, then link the current node to it and jump over it.

From brute force to the pattern: Collecting values into an array and building a new list works but allocates the whole list again. Inserting in place while walking once is O(n · log(max value)) for the GCDs.

Spot it: Pure in-place pointer surgery: for each pair (cur, cur.next), create a node, point it at cur.next, point cur at it, then jump over it to the old next node.

Approach:

  1. cur = head.
  2. While cur.next exists: compute g = gcd(cur.val, cur.next.val).
  3. Create ListNode(g, cur.next): it already points at the old next node. Then set cur.next to the new node.
  4. Jump cur two steps (over the new node) to the old next node, and repeat.
class Solution:
    def insertGreatestCommonDivisors(self, head: Optional[ListNode]) -> Optional[ListNode]:
        def gcd(a, b):
            # Euclid: gcd(a, b) = gcd(b, a % b), and gcd(a, 0) = a
            while b != 0:
                a, b = b, a % b
            return a

        cur = head
        while cur is not None and cur.next is not None:
            after = cur.next
            # the new node points at the old next node ...
            middle = ListNode(gcd(cur.val, after.val), after)
            # ... and cur points at the new node
            cur.next = middle
            # skip over the node we just added
            cur = after
        return head

What the code is doing, line by line:

  1. def gcd(a, b): Euclid's algorithm: replace (a, b) with (b, a % b) until b is 0; then a is the GCD. (Python's math.gcd does the same.)
  2. while cur is not None and cur.next is not None: we need a pair, so the next node must exist.
  3. after = cur.next: remember the old neighbour before we change any pointer.
  4. middle = ListNode(gcd(...), after): build the new node already linked to after.
  5. cur.next = middle: link it in. The order matters: if we changed cur.next first without saving after, we would lose the rest of the list.
  6. cur = after: move to the next original node, skipping the one we inserted.

Dry run on the example:

head = [18, 6, 10, 3] (LeetCode Example 1). Returns [18, 6, 6, 2, 10, 1, 3].

curaftergcdlist now
186618 → 6 → 6 → 10 → 3
610218 → 6 → 6 → 2 → 10 → 3
103118 → 6 → 6 → 2 → 10 → 1 → 3
3None—stop

Edge cases to test:

  • A single node → nothing to insert.
  • Equal neighbours: the GCD is the value itself.
  • Coprime neighbours → 1.

Complexity: O(n · log(max value)) time (one GCD per pair), O(1) extra space besides the new nodes.

Medium 86. Partition List Google Meta Amazon Microsoft Linked List

Problem: Rearrange the list so all nodes with values less than x come before nodes ≥ x, preserving the original order within each group.

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], x = 2
Output: [1,2]

Constraints: The number of nodes in the list is in the range [0, 200]. · -100 ≤ Node.val ≤ 100 · -200 ≤ x ≤ 200

Hint 1

Think of building two separate lists while walking the original once.

Hint 2

Use two dummy heads, then connect the end of the first list to the start of the second.

From brute force to the pattern: Collecting values into two arrays and rebuilding new nodes is O(n) time but O(n) extra space. Re-linking the existing nodes into two chains needs O(1) space.

Spot it: Splitting one list into two order-preserving groups is in-place pointer surgery: walk once and append each node to the tail of one of two dummy-headed lists, then join them.

Why not another pattern? Copying values into arrays and rebuilding works but allocates new nodes. Swapping values in place would break the relative order.

Approach:

  1. Create two dummy heads: small and big, with tails.
  2. Walk the list; append each node to the small tail if val < x, else to the big tail.
  3. End the big list with None (its last node may still point somewhere), then link small_tail.next = big_head.next.
class Solution:
    def partition(self, head: Optional[ListNode], x: int) -> Optional[ListNode]:
        small = ListNode()  # dummy head for values < x
        big = ListNode()  # dummy head for values >= x
        small_tail = small
        big_tail = big
        cur = head
        while cur is not None:
            if cur.val < x:
                small_tail.next = cur  # append to the "small" list
                small_tail = cur
            else:
                big_tail.next = cur  # append to the "big" list
                big_tail = cur
            cur = cur.next
        big_tail.next = None  # cut off the old link to avoid a cycle
        small_tail.next = big.next  # small list, then big list
        return small.next

What the code is doing, line by line:

  1. Two dummy heads mean the first node of either group needs no special case.
  2. Each node is re-linked onto one of the two tails in the order it's visited, which preserves relative order.
  3. big_tail.next = None: the last big node might still point to a small node from the original list.
  4. small_tail.next = big.next joins the groups; small.next is the new head.

Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.

Edge cases to test:

  • All values less than x (or all ≥ x).
  • An empty list.
  • Forgetting big_tail.next = None creates a cycle.

Complexity: O(n) time, O(1) extra space.

Medium 109. Convert Sorted List to Binary Search Tree Google Amazon Linked ListBST

Problem: Convert a sorted singly linked list into a height-balanced binary search tree.

Example 1
Input: head = [-10,-3,0,5,9]
Output: [0,-3,9,-10,null,5]
Explanation: One possible answer is [0,-3,9,-10,null,5], which represents the shown height balanced BST.
Example 2
Input: head = []
Output: []

Constraints: The number of nodes in head is in the range [0, 2 * 10⁴]. · -10⁵ ≤ Node.val ≤ 10⁵

Hint 1

A BST's in-order traversal is sorted, just like the list.

Hint 2

Build the tree in in-order, consuming one list node each time you create a root.

From brute force to the pattern: Finding the middle node at every level with fast/slow pointers costs O(n) per level: O(n log n). Building in in-order uses each list node exactly once at the moment it's needed: O(n).

Spot it: The list is the BST's in-order sequence. Build the tree in in-order: recursively build the left half, take the current list node as the root, then build the right half, advancing one pointer through the list as you go.

Why not another pattern? Copying the list into an array and taking middles (LeetCode 108) is O(n) time but O(n) extra space. Finding the middle with fast/slow at every level is O(n log n).

Approach:

  1. Count the nodes n.
  2. build(lo, hi) builds the tree for list positions lo..hi. If lo > hi, return None.
  3. mid = (lo + hi) // 2; build the left subtree for lo..mid − 1 first; this consumes exactly the nodes before mid.
  4. The current list node is now position mid: make it the root, advance the list pointer, then build the right subtree mid + 1..hi.
class Solution:
    def sortedListToBST(self, head: Optional[ListNode]) -> Optional[TreeNode]:
        n = 0
        cur = head
        while cur is not None:
            n = n + 1  # count the nodes
            cur = cur.next
        self.cur = head  # the next list node to place (in-order)

        def build(lo, hi):
            if lo > hi:
                return None
            mid = (lo + hi) // 2
            left = build(lo, mid - 1)  # all nodes before mid are consumed here
            root = TreeNode(self.cur.val)  # the current list node is position mid
            self.cur = self.cur.next
            root.left = left
            root.right = build(mid + 1, hi)
            return root

        return build(0, n - 1)

What the code is doing, line by line:

  1. Counting first lets build split ranges by index, exactly like the array version.
  2. self.cur walks the list in order; an in-order construction visits positions in increasing order too, so they stay in sync.
  3. The left subtree is built before the root is created, so by then self.cur points at position mid.
  4. Picking the middle of each range keeps the tree balanced.

Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.

Edge cases to test:

  • An empty list → None.
  • One or two nodes.
  • Negative values.

Complexity: O(n) time, O(log n) recursion depth.

Medium 147. Insertion Sort List Google Amazon Linked List

Problem: Sort a singly linked list using insertion sort and return its head.

Example 1
Input: head = [4,2,1,3]
Output: [1,2,3,4]
Example 2
Input: head = [-1,5,3,4,0]
Output: [-1,0,3,4,5]

Constraints: The number of nodes in the list is in the range [1, 5000]. · -5000 ≤ Node.val ≤ 5000

Hint 1

Keep a separate sorted list and insert nodes into it one by one.

Hint 2

A dummy head makes inserting at the front the same as anywhere else.

From brute force to the pattern: Copying values into an array, sorting and rebuilding uses O(n) space. Splicing nodes keeps it in place.

Spot it: Insertion sort on a linked list is pure pointer surgery: detach each node and splice it into its place in a growing sorted list behind a dummy head (no shifting of elements needed).

Why not another pattern? Merge sort (LeetCode 148) is O(n log n) and faster; this problem asks specifically for insertion sort (O(n²) worst case).

Approach:

  1. dummy starts an empty sorted list.
  2. Take nodes from the input one at a time (remember next before re-linking).
  3. Walk prev from dummy until prev.next is None or larger than the node; splice the node after prev.
class Solution:
    def insertionSortList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        dummy = ListNode()  # head of the sorted part
        cur = head
        while cur is not None:
            nxt = cur.next  # remember the rest of the unsorted list
            prev = dummy
            while prev.next is not None and prev.next.val < cur.val:
                prev = prev.next  # find where cur belongs
            cur.next = prev.next  # splice cur in after prev
            prev.next = cur
            cur = nxt
        return dummy.next

What the code is doing, line by line:

  1. dummy.next is the sorted list built so far (initially empty).
  2. nxt = cur.next: save it before cur is re-linked into the sorted list.
  3. The inner loop stops at the last node smaller than cur.
  4. Two pointer assignments insert cur there.

Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.

Edge cases to test:

  • Already sorted (each insertion walks to the end; you can optimise by starting from the last inserted node).
  • Reverse sorted.
  • Duplicates.

Complexity: O(n²) time in the worst case, O(1) extra space.

Medium 237. Delete Node in a Linked List Google Amazon Microsoft Linked List

Problem: You're given only the node to delete (not the head). It's not the last node. Delete it so the list's values no longer contain it.

Example 1
Input: head = [4,5,1,9], node = 5
Output: [4,1,9]
Explanation: You are given the second node with value 5, the linked list should become 4 -> 1 -> 9 after calling your function.
Example 2
Input: head = [4,5,1,9], node = 1
Output: [4,5,9]
Explanation: You are given the third node with value 1, the linked list should become 4 -> 5 -> 9 after calling your function.

Constraints: The number of the nodes in the given list is in the range [2, 1000]. · -1000 ≤ Node.val ≤ 1000 · The value of each node in the list is unique. · The node to be deleted is in the list and is not a tail node.

Hint 1

You can't reach the node before node. Which node can you remove easily?

Hint 2

Copy the next node's value into node, then delete the next node.

From brute force to the pattern: Normally you'd find the previous node from the head (O(n)), but there's no head. The copy-and-skip trick is O(1).

Spot it: A linked-list rewiring puzzle: without the previous node you can't unlink node itself, but you can copy the next node's value into it and unlink the next node.

Why not another pattern? Walking from the head to find the previous node is impossible here: you aren't given the head.

Approach:

  1. Copy node.next.val into node.val.
  2. Skip over the next node: node.next = node.next.next.
class Solution:
    def deleteNode(self, node):
        node.val = node.next.val  # take over the next node's value
        node.next = node.next.next  # and remove the next node

What the code is doing, line by line:

  1. We can't change the previous node's pointer, so we make node look like its successor.
  2. Then the successor (now a duplicate) is unlinked.

Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.

Edge cases to test:

  • Deleting the second-to-last node.
  • Equal values in the list.
  • The problem guarantees node isn't the tail.

Complexity: O(1) time and space.

Medium 328. Odd Even Linked List Google Meta Amazon Apple Linked List

Problem: Group all nodes at odd positions first, then those at even positions (1-indexed), keeping the order inside each group, in O(1) space.

Example 1
Input: head = [1,2,3,4,5]
Output: [1,3,5,2,4]
Example 2
Input: head = [2,1,3,5,6,4,7]
Output: [2,3,6,7,1,5,4]

Constraints: The number of nodes in the linked list is in the range [0, 10⁴]. · -10⁶ ≤ Node.val ≤ 10⁶

Hint 1

Build two chains at the same time: positions 1, 3, 5, … and 2, 4, 6, …

Hint 2

At the end, connect the last odd node to the first even node.

From brute force to the pattern: Collecting nodes into two arrays and re-linking uses O(n) space; weaving the chains in place uses O(1).

Spot it: Splitting a list into two interleaved chains and re-joining them is in-place pointer surgery with two moving pointers.

Why not another pattern? Copying values into arrays and rebuilding uses O(n) extra space, which the problem forbids.

Approach:

  1. odd = head, even = head.next, remember even_head.
  2. While even and even.next exist: odd.next = even.next, advance odd; even.next = odd.next, advance even.
  3. Attach the even chain: odd.next = even_head.
class Solution:
    def oddEvenList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        if head is None:
            return None
        odd = head
        even = head.next
        even_head = even  # remember where the even chain starts
        while even is not None and even.next is not None:
            odd.next = even.next  # next odd node
            odd = odd.next
            even.next = odd.next  # next even node
            even = even.next
        odd.next = even_head  # odds first, then evens
        return head

What the code is doing, line by line:

  1. odd and even are the tails of the two chains being built.
  2. Each round links one odd node and one even node, skipping over the other chain's node.
  3. The loop condition guarantees even.next (the next odd node) exists.
  4. Finally the odd chain's tail points to the first even node.

Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.

Edge cases to test:

  • An empty list or a single node.
  • Two nodes → unchanged.
  • Odd and even lengths.

Complexity: O(n) time, O(1) space.

Medium 707. Design Linked List Google Linked List

Problem: Implement a singly linked list with get(index), addAtHead, addAtTail, addAtIndex(index, val) and deleteAtIndex(index).

Example 1
Input: ["MyLinkedList", "addAtHead", "addAtTail", "addAtIndex", "get", "deleteAtIndex", "get"] [[], [1], [3], [1, 2], [1], [1], [1]]
Output: [null, null, null, null, 2, null, 3]
Explanation: MyLinkedList myLinkedList = new MyLinkedList(); myLinkedList.addAtHead(1); myLinkedList.addAtTail(3); myLinkedList.addAtIndex(1, 2); // linked list becomes 1->2->3 myLinkedList.get(1); // return 2 myLinkedList.deleteAtIndex(1); // now the linked list is 1->3 myLinkedList.get(1); // return 3

Constraints: 0 ≤ index, val ≤ 1000 · Please do not use the built-in LinkedList library. · At most 2000 calls will be made to get, addAtHead, addAtTail, addAtIndex and deleteAtIndex.

Hint 1

Most operations need the node before the target position.

Hint 2

A dummy head and a size counter remove almost all special cases.

From brute force to the pattern: Without a sentinel, inserting or deleting at index 0 needs separate code paths; the dummy node makes every position uniform.

Spot it: Every operation is "walk to the node before position index, then re-link". A dummy (sentinel) head makes index 0 behave like any other position, and a size counter answers bounds checks in O(1).

Why not another pattern? A Python list would make this trivial, but the exercise is about the pointer operations themselves.

Approach:

  1. Keep self.dummy (sentinel before the real head) and self.size.
  2. A helper _before(index) walks index steps from the dummy: it returns the node just before position index.
  3. addAtIndex: reject index > size; splice a new node after _before(index). Head and tail additions are addAtIndex(0, …) and addAtIndex(size, …).
  4. get and deleteAtIndex: reject index ≥ size; use _before(index) to read or unlink.
class MyLinkedList:
    class Node:
        def __init__(self, val, nxt=None):
            self.val = val
            self.next = nxt

    def __init__(self):
        self.dummy = self.Node(0)  # sentinel in front of the real head
        self.size = 0

    def _before(self, index):
        node = self.dummy
        for _ in range(index):
            node = node.next  # walk to the node just before position index
        return node

    def get(self, index: int) -> int:
        if index < 0 or index >= self.size:
            return -1
        return self._before(index).next.val

    def addAtHead(self, val: int) -> None:
        self.addAtIndex(0, val)

    def addAtTail(self, val: int) -> None:
        self.addAtIndex(self.size, val)

    def addAtIndex(self, index: int, val: int) -> None:
        if index < 0 or index > self.size:
            return  # invalid position: do nothing
        prev = self._before(index)
        prev.next = self.Node(val, prev.next)  # splice the new node in
        self.size = self.size + 1

    def deleteAtIndex(self, index: int) -> None:
        if index < 0 or index >= self.size:
            return
        prev = self._before(index)
        prev.next = prev.next.next  # unlink the node at index
        self.size = self.size - 1

What the code is doing, line by line:

  1. self.dummy sits before position 0, so inserting at the head is the same code as inserting anywhere.
  2. _before(index) takes index steps from the dummy, landing on the predecessor of position index.
  3. addAtIndex allows index == size (append) but not beyond it.
  4. get and deleteAtIndex require an existing position: index < size.

Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.

Edge cases to test:

  • addAtIndex(size, val) appends.
  • addAtIndex(size + 1, val) does nothing.
  • Deleting the only node.

Complexity: O(index) per operation (O(n) worst case), O(1) for addAtHead; O(n) space.

Medium 2058. Find the Minimum and Maximum Number of Nodes Between Critical Points Google Linked List

Problem: A critical point is a local maximum or minimum (strictly greater or smaller than both neighbours). Return [min distance, max distance] between any two critical points, or [-1, -1] if there are fewer than two.

Example 1
Input: head = [3,1]
Output: [-1,-1]
Explanation: There are no critical points in [3,1].
Example 2
Input: head = [5,3,1,2,5,1,2]
Output: [1,3]
Explanation: There are three critical points: - [5,3,1,2,5,1,2]: The third node is a local minima because 1 is less than 3 and 2. - [5,3,1,2,5,1,2]: The fifth node is a local maxima because 5 is greater than 2 and 1. - [5,3,1,2,5,1,2]: The sixth node is a local minima because 1 is less than 5 and 2. The minimum distance is between the fifth and the sixth node. minDistance = 6 - 5 = 1. The maximum distance is between the third and the sixth node. maxDistance = 6 - 3 = 3.

Constraints: The number of nodes in the list is in the range [2, 10⁵]. · 1 ≤ Node.val ≤ 10⁵

Hint 1

You need each node together with its two neighbours.

Hint 2

The smallest gap is between consecutive critical points; the largest is between the first and the last.

From brute force to the pattern: Collecting every critical index and comparing all pairs is O(k²). Only consecutive gaps and the first/last pair matter, so one pass suffices.

Spot it: A single walk looking at each node with its neighbours (prev, cur, cur.next): linked-list traversal with a few remembered indices.

Why not another pattern? Collecting all critical indices in a list first works too (O(k) extra space); you only need the first, previous and latest ones.

Approach:

  1. Walk with prev and cur, keeping the position i of cur.
  2. If cur is a local max or min, it's critical: compare with the previous critical index for the minimum gap, remember the first one for the maximum.
  3. At the end, the maximum distance is last − first; return [-1, -1] if fewer than two critical points.
class Solution:
    def nodesBetweenCriticalPoints(self, head: Optional[ListNode]) -> List[int]:
        first = -1  # index of the first critical point
        last = -1  # index of the latest critical point
        min_gap = float("inf")
        prev = head
        cur = head.next
        i = 1
        while cur is not None and cur.next is not None:
            nxt = cur.next
            if (cur.val > prev.val and cur.val > nxt.val) or (cur.val < prev.val and cur.val < nxt.val):
                if first == -1:
                    first = i  # the first critical point
                else:
                    min_gap = min(min_gap, i - last)  # distance to the previous critical point
                last = i
            prev = cur
            cur = nxt
            i = i + 1
        if min_gap == float("inf"):
            return [-1, -1]  # fewer than two critical points
        return [min_gap, last - first]

What the code is doing, line by line:

  1. The first and last nodes can't be critical (they lack a neighbour), so the walk starts at index 1 and stops before the tail.
  2. A node is critical when it's strictly above both neighbours or strictly below both.
  3. The minimum distance is always between two consecutive critical points, so comparing with last is enough.
  4. The maximum distance is between the first and the last critical point.

Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.

Edge cases to test:

  • Fewer than three nodes → [-1, -1].
  • Exactly one critical point → [-1, -1].
  • Equal neighbours are not critical (strictly greater/smaller is required).

Complexity: O(n) time, O(1) space.

Medium 708. Insert into a Sorted Circular Linked List Meta Linked List

Problem: Insert insertVal into a sorted circular linked list (given any node) so it stays sorted; return the given node (or the new one if empty).

Example 1
Input: head = [3,4,1], insertVal = 2
Output: [3,4,1,2]
Explanation: 2 goes between 1 and 3, after the wrap point.
Example 2
Input: head = [], insertVal = 1
Output: [1]
Example 3
Input: head = [1], insertVal = 0
Output: [1,0]

Constraints: The number of nodes in the list is in the range [0, 5 * 10⁴] · -10⁶ ≤ Node.val, insertVal ≤ 10⁶

Hint 1

Where can the new value go in a sorted circle?

Hint 2

Handle the place where the list wraps from its largest to its smallest value.

From brute force to the pattern: Copying to an array and rebuilding is O(n) space; one walk is O(1).

Spot it: A linked-list walk with three cases: a gap prev ≤ x ≤ next; the wrap point (prev > next), where a new maximum or minimum belongs; or a full circle with all equal values (insert anywhere).

Why not another pattern? Finding the minimum first and then scanning works, but one loop handles every case.

Approach:

  1. Empty list: a single node pointing to itself.
  2. Walk prev = head, cur = head.next once around.
  3. Insert when prev.val ≤ x ≤ cur.val, or at the wrap (prev.val > cur.val) when x ≥ prev.val or x ≤ cur.val.
  4. If no spot was found after a full circle (all equal), insert after prev.
class Solution:
    def insert(self, head: "Optional[Node]", insertVal: int) -> "Node":
        node = Node(insertVal)
        if head is None:
            node.next = node  # a circle of one
            return node
        prev, cur = head, head.next
        while True:
            if prev.val <= insertVal <= cur.val:
                break  # fits between two neighbours
            if prev.val > cur.val and (insertVal >= prev.val or insertVal <= cur.val):
                break  # at the wrap point: new max or new min
            prev, cur = cur, cur.next
            if prev is head:
                break  # went all the way round (all values equal)
        prev.next = node
        node.next = cur
        return head

What the code is doing, line by line:

  1. The wrap point is the only place where values drop; values beyond either end go there.
  2. If every value is equal, no condition fires before returning to head, and any position is sorted.

Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.

Edge cases to test:

  • Empty list.
  • All values equal.
  • New maximum or minimum.

Complexity: O(n) time, O(1) space.

Medium 1721. Swapping Nodes in a Linked List Amazon Linked List

Problem: Swap the values of the k-th node from the start and the k-th node from the end.

Example 1
Input: head = [1,2,3,4,5], k = 2
Output: [1,4,3,2,5]
Example 2
Input: head = [7,9,6,6,7,8,3,0,9,5], k = 5
Output: [7,9,6,6,8,7,3,0,9,5]

Constraints: The number of nodes in the list is n. · 1 ≤ k ≤ n ≤ 10⁵ · 0 ≤ Node.val ≤ 100

Hint 1

How do you find the k-th node from the end in one pass?

Hint 2

Keep two pointers k − 1 apart.

From brute force to the pattern: Copying to an array is O(n) space; two pointers are O(1).

Spot it: The k-th from the end is found with the gap technique: once a pointer has gone k steps, start a second pointer at the head; when the first reaches the end, the second is k from the end. Then swap the two values.

Why not another pattern? Counting the length first works too (two passes); copying to an array uses O(n) space.

Approach:

  1. Walk k − 1 steps from the head: that's first.
  2. fast = first, second = head; advance both until fast is the last node.
  3. Swap first.val and second.val.
class Solution:
    def swapNodes(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
        first = head
        for _ in range(k - 1):
            first = first.next  # k-th from the start
        fast, second = first, head
        while fast.next:
            fast = fast.next  # keep a gap of k - 1 nodes
            second = second.next
        first.val, second.val = second.val, first.val  # k-th from the end is `second`
        return head

What the code is doing, line by line:

  1. When fast is at the last node, second is k − 1 nodes behind it: the k-th from the end.
  2. Swapping values is allowed and avoids relinking edge cases.

Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.

Edge cases to test:

  • Both positions are the same node (odd middle).
  • k = 1 (first and last).
  • Two nodes.

Complexity: O(n) time, O(1) space.