CodeGround
DashboardProblemsInterviewsAssessmentsCourses
CodeGround
DashboardProblemsInterviewsAssessmentsCourses
The Technical Interview Course

0 of 170 lessons complete

  • Changing links without losing the list
  • The dummy head
  • Two pointers at different speeds
  • A fixed gap between two pointers
  • Composing three things you already have
  • Mapping old nodes to new ones
  • Walking two lists at once with a carry
  • When an array is secretly a linked list
  • A hash map over a doubly linked list
  • Merging many by merging two, repeatedly
  • Reversing in fixed groups
  • Retrieval: the four habits

Linked Lists

60 / 170

Linked Lists/Lesson 5 of 12·About 71 min

Composing three things you already have

The hard-looking problems in this unit are usually three easy ones in a row.

Key idea

The strategy

Some list problems describe a final arrangement that looks nothing like the input, and there is no single loop that produces it. The way in is to ask what sequence of operations you already know would get there.

You can find the middle. You can reverse a list. You can merge two lists. A great many rearrangements are exactly those three, applied in order.

A worked composition

Here is a different rearrangement built the same way: move the second half of a list to the front, keeping both halves in order.

1class Node:2    def __init__(self, val, next=None):3        self.val, self.next = val, next4 5def build(values):6    head = None7    for value in reversed(values):8        head = Node(value, head)9    return head10 11def to_list(node):12    out = []13    while node:14        out.append(node.val)15        node = node.next16    return out17 18def split_at_middle(head):19    """Split in two, giving the first half the extra node when odd."""20    if head is None or head.next is None:21        return head, None22    slow, fast = head, head.next        # offset start gives the first middle23    while fast and fast.next:24        slow = slow.next25        fast = fast.next.next26    second = slow.next27    slow.next = None                    # the cut, and it must happen28    return head, second29 30def append(first, second):31    if first is None:32        return second33    tail = first34    while tail.next:35        tail = tail.next36    tail.next = second37    return first38 39def rotate_halves(head):40    first, second = split_at_middle(head)41    return append(second, first)42 43print(to_list(rotate_halves(build([1, 2, 3, 4]))))44print(to_list(rotate_halves(build([1, 2, 3, 4, 5]))))

Why it works

The cut is the step people forget

slow.next = None is what actually separates the halves. Without it, the first half still runs all the way through the second, and everything downstream operates on a list you thought you had split.

This produces bugs that look impossible: a reversal that seems to work but leaves a cycle, or a merge that never terminates. If a composed solution loops forever, the missing cut is the first thing to check.

Tip

Starting fast one node ahead

The split above starts fast at head.next rather than at head. That is the trick from the last lesson for landing on the first middle, which is what you need in order to cut before the second half begins.

Same loop, one different starting position, different answer. Worth noticing that the adjustment is made at initialization rather than inside the loop.

Key idea

Interleaving two lists

The last piece some rearrangements need is alternating nodes from two lists rather than merging them by value. The loop takes one from each, saving both next pointers before rewiring, which is the three-reference discipline from the first lesson applied twice at once.

Write it on paper with two three-node lists before coding it. The order of the four assignments is the entire problem, and it is much easier to get right from a drawing than from a guess.

Plan before you type

For the problem below, write down the sequence of operations first, in words. Something like: find the middle, cut, reverse the second part, then interleave.

With that written down it is three functions you have already built and one loop to write. Without it, it is a single enormous function with four pointers and no way to tell which one is wrong.

Code lab· 30 min

The pieces

Build the three operations separately so the composition is assembly. reverse reverses a list and returns the new head. split_at_middle returns the two halves, giving the first the extra node when the length is odd, and cuts the link between them. interleave alternates nodes from two lists, starting with the first, and appends anything left over.

solution.py

Tests

print(to_list(reverse(build([1, 2, 3]))), to_list(reverse(build([]))))a, b = split_at_middle(build([1, 2, 3, 4]))print(to_list(a), to_list(b))a, b = split_at_middle(build([1, 2, 3, 4, 5]))print(to_list(a), to_list(b))print(to_list(interleave(build([1, 2]), build([9, 8]))))print(to_list(interleave(build([1]), build([9, 8, 7]))))

Output

Run tests to see output.

Problem· 30 min

Reorder List

Read the constraints first and let them tell you what complexity is expected. Derive the approach, implement it, run the tests, and submit when it passes.

PreviousA fixed gap between two pointers
NextMapping old nodes to new ones

0 of 3 parts complete