DSA sheet · Linked List · Reversal pattern
Reverse Linked List II
So far the reversal pattern reversed a whole list (or a whole half). Now we reverse only a window: from position left to position right, and the rest of the list must stay where it is. The teacher first shows the copy-into-an-array brute force, then the in-place solution: walk to the node just before the window, reverse exactly right − left + 1 nodes with the usual prev / curr / nxt, then stitch the two ends back in. She also shows, by running a failing test on screen, why we need a dummy node and must return dummy.next instead of head. The "reverse a window and reconnect" move is the core of Reverse Nodes in k-Group later.
Every part below follows the same order:
① the question in simple words → ② what the constraints tell us → ③ intuition → ④ building the logic from examples → ⑤ approach steps → ⑥ code → ⑦ code line by line → ⑧ dry run → ⑨ complexity & remember
- Part 0 · Linked lists, reversal and the dummy node from scratch
- Part A · Brute force: copy values, swap the window, write back
- Part B · Optimal: in-place window reversal with a dummy node
- Part C · Revision page
Part 0 · What you must know before starting
Nodes, head, None and walking
A linked list is a chain of nodes. Each node has a value val and a link next to the node after it. The last node points to None. We're given the first node, the head. To move along we use a walker: curr = curr.next.
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next[1] → [2] → [3] → [4] → [5] → [6] → None head position: 1 2 3 4 5 6 (this problem counts from 1)
There's no index access: to reach position i you must follow i − 1 arrows from the head (O(i) time). And you can't step backwards.
Save next before changing a pointer
A node's next is the only road to everything after it. Overwrite it without saving and that part of the list is lost. So we always store nxt = curr.next first.
Reversal with prev / curr / nxt (from "Reverse a Linked List")
nxt = curr.next # 1. save the road ahead curr.next = prev # 2. flip this node's arrow backward prev = curr # 3. prev moves up curr = nxt # 4. curr moves up
Two facts we'll use: after flipping some nodes, prev is on the last node flipped (the new front of the reversed piece), and curr is on the first node not flipped (just outside the piece).
The dummy node
A dummy is a fake extra node we put in front of the head: dummy = ListNode(0, head). Its value doesn't matter.
[D] → [1] → [2] → [3] → ... dummy head
Why bother? Many list operations need "the node before" some position. Every real node has one, except the head. Without a dummy you'd need special code for "what if the change starts at the head?". With a dummy, even the head has a node before it, so one piece of code handles every case. And at the end, dummy.next is always the real first node, even if the old head moved somewhere else.
In-place
The teacher's term: a linked-list solution is in-place when it only rewires the existing nodes and uses no extra list or array. That's what "optimal" means here.
Part A · Brute force: copy values, swap the window, write back
LeetCode 92
1The question in simple words
You get head and two positions left ≤ right (counting from 1). Reverse only the nodes from position left to position right and return the head of the resulting list.
input: [1] → [2] → [3] → [4] → [5] → [6] → None, left = 2, right = 4
└─── reverse this ───┘
output: [1] → [4] → [3] → [2] → [5] → [6] → None
That's the teacher's list. LeetCode's examples: [1,2,3,4,5], left=2, right=4 → [1,4,3,2,5] and [5], left=1, right=1 → [5].
2What the constraints tell us
- Number of nodes: 1 to 500 → small. Any linear idea passes easily. As usual in linked lists, she treats 2n, 3n, 4n passes as "brute force" and one clean n (or n/2) pass as "optimal".
1 ≤ left ≤ right ≤ n→ the window is always inside the list.leftcan be 1 (window starts at the head, see the dummy node), andrightcan be n (window reaches the tail). Both together means "reverse everything".left == rightis allowed → a window of one node. Reversing one node changes nothing.- Values −500 to 500 → nothing special.
3Intuition: pretend we can't reverse a list yet
If we copy the values into an array, the window is just a range of indices. Position left is index left − 1, position right is index right − 1. Reversing a range of an array is the classic two-pointer swap. Then we write the array back into the nodes.
4Building the logic
vals = [1, 2, 3, 4, 5, 6] left = 2 → i = 1, right = 4 → j = 3
i=1, j=3 → swap → [1, 4, 3, 2, 5, 6]
i=2, j=2 → they meet → stop (the 3 stays)
- Swap with a temporary variable (her Java code uses
temp; Python can also doa, b = b, a). - Stop while
i < j: odd-sized windows end with i == j (the middle stays), even-sized ones end with i > j. - Then walk the list from head again and overwrite every node's value with
vals[k], k = 0, 1, 2, … The nodes outside the window simply get their own value back. - The links never change, so the first node is still the first node → return
head.
5Approach steps
- Copy every value into
vals. i = left - 1,j = right - 1. Whilei < j: swapvals[i],vals[j]; i += 1; j -= 1.- Walk the list again from head, writing
vals[0],vals[1], … into the nodes. - Return head.
6Code (Python)
class Solution:
def reverseBetween(self, head, left, right):
vals = []
curr = head
while curr: # 1. copy values out
vals.append(curr.val)
curr = curr.next
i, j = left - 1, right - 1 # 2. positions → indexes
while i < j: # reverse the window in the array
temp = vals[i]
vals[i] = vals[j]
vals[j] = temp
i += 1
j -= 1
curr = head # 3. write everything back
k = 0
while curr:
curr.val = vals[k]
k += 1
curr = curr.next
return head7Code line by line
| line | what it means |
|---|---|
| vals = [] … while curr: … | Pass 1: n steps, values copied into an O(n) array. |
| i, j = left - 1, right - 1 | The problem counts positions from 1, Python indexes from 0. |
| while i < j: | Swap the outer pair, move inwards, stop when the pointers meet or cross. |
| temp = vals[i] … | A normal swap through a temporary variable. |
| curr = head; k = 0; while curr: … | Pass 2 over the list: overwrite each node's value in order. |
| return head | Links unchanged, so the head is still the first node. |
8Dry run
| step | i, j | vals after | list after |
|---|---|---|---|
| copy | – | [1, 2, 3, 4, 5, 6] | [1] → [2] → [3] → [4] → [5] → [6] |
| swap 1 | 1, 3 | [1, 4, 3, 2, 5, 6] | (unchanged so far) |
| stop | 2, 2 | i < j false | |
| write | – | – | [1] → [4] → [3] → [2] → [5] → [6] |
9Complexity & remember
- Time: n (copy) + (right − left + 1)/2 (swaps) + n (write back). In the worst case the window is the whole list (left = 1, right = n), so it's n + n/2 + n = 5n/2. Linear, but three passes.
- Space O(n) for the array.
left−1 … right−1 with two pointers → write all values back → return head. O(5n/2) time, O(n) space.Part B · Optimal: in-place window reversal with a dummy node
1The question (same), new goal
Same output, but in-place (no array) and in one pass. LeetCode's follow-up asks exactly that.
2What the constraints tell us
leftcan be 1 → the window can start at the head, which has no node before it → dummy node.left == right→ nothing to reverse → return head straight away.- n ≥ 1, so head is never None. We still keep
head is Nonein the base case, the way the teacher says to always think about it.
3Intuition: reverse the window, then fix its two ends
Stand on the first node of the window, [2], and run the normal reversal over the window only. When it's done:
previs on[4], the last window node, which is now the front of the reversed piece.curris on[5], the first node after the window.[2], the old front of the window, is now its back.
The reversed piece is correct inside, but its two ends are loose. Two links fix it:
- the node before the window (
[1]) must point to the new front[4]=prev; - the old front
[2]must point to the node after the window[5]=curr.
So before reversing we need a pointer to the node before the window. The teacher calls it previous-left (prev_left), and the old front [2] she calls the left node ("leftover node", left_node).
4Building the logic step by step
Step 1: put a dummy in front and walk left − 1 jumps
prev_left starts at the dummy and jumps left − 1 times. With left = 2 that's 1 jump, landing on [1], the node just before position 2. Then curr = prev_left.next is the first window node and prev = None.
start: prev_left = dummy [D] → [1] → [2] → [3] → [4] → [5] → [6] → None dummy/prev_left left − 1 = 1 jump: prev_left = prev_left.next [D] → [1] → [2] → [3] → [4] → [5] → [6] → None dummy ↑ prev_left curr = prev_left.next, prev = None [D] → [1] → [2] → [3] → [4] → [5] → [6] → None dummy ↑ curr prev_left
left − 1 times?→ The dummy sits at "position 0". After k jumps you're at position k. We want the node at position
left − 1, the one right before the window. If left = 1, that's 0 jumps: prev_left stays on the dummy, which is exactly "the node before the head". Without the dummy, left = 1 would have no node to stand on.Step 2: reverse exactly right − left + 1 nodes
The window holds right − left + 1 nodes (positions 2, 3, 4 → 3 nodes). We run the four reversal lines that many times. The red arrow is the one just flipped. Only the window piece is drawn on the main line; the part before it is unchanged.
[D] → [1] → [2] ([1] still points to [2] the whole time) ① nxt = curr.next (→ [3]) None [2] → [3] → [4] → [5] → [6] → None prev curr nxt ② curr.next = prev ([2].next: [3] → None) None ← [2] [3] → [4] → [5] → [6] → None prev curr nxt ③ prev = curr, curr = nxt None ← [2] [3] → [4] → [5] → [6] → None prev curr
[D] → [1] → [2] ([1] still points to [2] the whole time) ① nxt = curr.next (→ [4]) None ← [2] [3] → [4] → [5] → [6] → None prev curr nxt ② curr.next = prev ([3].next: [4] → [2]) None ← [2] ← [3] [4] → [5] → [6] → None prev curr nxt ③ prev = curr, curr = nxt None ← [2] ← [3] [4] → [5] → [6] → None prev curr
[D] → [1] → [2] ([1] still points to [2] the whole time) ① nxt = curr.next (→ [5]) None ← [2] ← [3] [4] → [5] → [6] → None prev curr nxt ② curr.next = prev ([4].next: [5] → [3]) None ← [2] ← [3] ← [4] [5] → [6] → None prev curr nxt ③ prev = curr, curr = nxt None ← [2] ← [3] ← [4] [5] → [6] → None prev curr
| round | pointers | which .next is rewired | pieces after the round | progress |
|---|---|---|---|---|
| 1 | prev_left=[1] · prev=None · curr=[2] · nxt=[3] | [2].next: [3] → None | [D] → [1] → [2] … reversed so far: [2] → None not yet: [3] → … → [6] | done 1 of 3 nodes |
| 2 | prev_left=[1] · prev=[2] · curr=[3] · nxt=[4] | [3].next: [4] → [2] | [D] → [1] → [2] … reversed so far: [3] → [2] → None not yet: [4] → … → [6] | done 2 of 3 nodes |
| 3 | prev_left=[1] · prev=[3] · curr=[4] · nxt=[5] | [4].next: [5] → [3] | [D] → [1] → [2] … reversed so far: [4] → [3] → [2] → None not yet: [5] → … → [6] | done 3 of 3 nodes |
while curr like in the full reversal?→
while curr would keep flipping past the window, into [5] and [6], reversing the whole tail. We must stop after exactly the window's nodes, so we run a counted loop: for _ in range(right - left + 1).prev starts as None, so [2].next becomes None. Isn't that wrong?→ Only for a moment. [2] is the old front, which will become the back of the window, and we'll point it to [5] in step 3. None is just a placeholder until then.
Step 3: stitch both ends
State after the loop (one picture, all pointers):
[D] → [1] → [2] ← [3] ← [4] [5] → [6] → None
↓
None
prev_left = [1] ([1].next is still [2]) prev = [4] curr = [5]
First grab the old front: left_node = prev_left.next → [2]. Nobody touched [1]'s arrow, so it still leads to [2].
left_node?→ The teacher shows two equally good moments: before the reversal (it's the node where curr starts,
prev_left.next), or after it, as long as it's before we overwrite prev_left.next. After that line, the only road to [2] from the front is gone. Our code saves it after the loop, right before the rewiring.Now the two links:
a) prev_left.next = prev ([1].next: [2] → [4]) [D] → [1] → [4] → [3] → [2] → None and [5] → [6] → None (loose for now) b) left_node.next = curr ([2].next: None → [5]) [D] → [1] → [4] → [3] → [2] → [5] → [6] → None ✓
Step 4: return dummy.next, not head
For the example above, head is still [1] and still first, so returning head would happen to work. The teacher shows why that's luck by changing the test on screen to [1,2,3,4,5], left = 1, right = 4:
before: [D] → [1] → [2] → [3] → [4] → [5] → None prev_left = dummy (0 jumps)
after: [D] → [4] → [3] → [2] → [1] → [5] → None
dummy.next head is still here
return head→ the list read from [1] is[1] → [5]→ wrong answer "1, 5" (this is what she got on screen).return dummy.next→4 → 3 → 2 → 1 → 5→ correct. The dummy always points at whatever is first now.
Base case
If left == right, the window is a single node; reversing it means nothing ("from node 2 to node 2" is no reversal). Also if there's no head there's nothing to do. Return head immediately.
left == right check required for correctness?→ Not strictly: with one round, [x].next becomes None, then gets reconnected to curr, so the list ends up the same. The check just skips useless work and makes the intent clear, which is why the teacher puts it first.
5Approach steps
- If head is None or left == right → return head.
dummy = ListNode(0, head);prev_left = dummy; jumpleft − 1times.prev = None,curr = prev_left.next.- Repeat
right − left + 1times: nxt = curr.next; curr.next = prev; prev = curr; curr = nxt. left_node = prev_left.next(old front of the window).prev_left.next = prev(before-window → new front).left_node.next = curr(new back → after-window).- Return
dummy.next.
6Code (Python)
class Solution:
def reverseBetween(self, head, left, right):
if head is None or left == right: # nothing to reverse
return head
dummy = ListNode(0, head) # a node before the head
prev_left = dummy
for _ in range(left - 1): # land on the node before the window
prev_left = prev_left.next
prev = None
curr = prev_left.next # first node of the window
for _ in range(right - left + 1): # flip exactly the window
nxt = curr.next
curr.next = prev
prev = curr
curr = nxt
# now: prev = new front of the window, curr = first node after it
left_node = prev_left.next # old front, will be the back
prev_left.next = prev # before-window → new front
left_node.next = curr # new back → after-window
return dummy.next # correct even when left == 17Code line by line
| line | what it means |
|---|---|
| if head is None or left == right: return head | Base cases: no list, or a one-node window. |
| dummy = ListNode(0, head) | Fake node in front, so even position 1 has a node before it. |
| for _ in range(left - 1): prev_left = prev_left.next | Starting at position 0 (dummy), take left − 1 jumps → position left − 1, the node before the window. Her Java loop "i from 1 while i != left" is the same left − 1 jumps. |
| curr = prev_left.next | The first node to reverse (position left). |
| for _ in range(right - left + 1): | Exactly the number of nodes in the window. |
| nxt = curr.next … curr = nxt | Standard reversal step. |
| left_node = prev_left.next | prev_left's arrow was never touched, so it still points at the old front. |
| prev_left.next = prev | Connect the part before the window to the reversed piece's new front. |
| left_node.next = curr | Connect the reversed piece's new back to the rest of the list (None if the window reached the tail). |
| return dummy.next | Whatever is first now. Returning head breaks when left == 1. |
8Dry runs
Teacher's example: [1..6], left = 2, right = 4
- left ≠ right → go on. dummy → [1]. prev_left jumps once → [1].
- curr = [2], prev = None. 3 rounds (drawn above): [2]→None, [3]→[2], [4]→[3]. Now prev = [4], curr = [5].
- left_node = [1].next = [2].
- [1].next = [4] → reading from the dummy: D, 1, 4, 3, 2, then None.
- [2].next = [5] → D, 1, 4, 3, 2, 5, 6.
- Return dummy.next = [1] → 1 → 4 → 3 → 2 → 5 → 6 ✓
Window at the head: [1..5], left = 1, right = 4
| step | pointers | what changes | list read from dummy |
|---|---|---|---|
| 1 | prev_left = dummy (0 jumps), curr = [1] | – | D → 1 → 2 → 3 → 4 → 5 |
| 2 | after 4 rounds: prev = [4], curr = [5] | [1]→None, [2]→[1], [3]→[2], [4]→[3] | D → 1 → None (piece: 4 → 3 → 2 → 1 → None) |
| 3 | left_node = dummy.next = [1] | – | |
| 4 | dummy.next = [4] | D → 4 → 3 → 2 → 1 → None | |
| 5 | [1].next = [5] | D → 4 → 3 → 2 → 1 → 5 |
Return dummy.next = [4] ✓. (head = [1] would give 1 → 5.)
Window at the tail: [1, 2, 3], left = 2, right = 3
prev_left = [1]; 2 rounds flip [2], [3]; prev = [3], curr = None. [1].next = [3], [2].next = None → 1 → 3 → 2 ✓. So left_node.next = curr also correctly ends the list when the window reaches the tail.
9Complexity & remember
- Time O(n): left − 1 jumps to reach the window, then right − left + 1 flips. Together that's at most right ≤ n steps, one pass, and we never look at nodes after the window.
- Space O(1): a dummy node and a handful of pointers.
left−1 to prev_left → flip right−left+1 nodes → left_node = prev_left.next, prev_left.next = prev, left_node.next = curr → return dummy.next.Part C · Revision page
| Brute force | Optimal | |
|---|---|---|
| idea | array + swap indexes left−1…right−1 + write back | walk to the node before the window, flip the window, reconnect the ends |
| changes | values | links (in-place) |
| left = 1 | no special case | handled by the dummy |
| returns | head | dummy.next |
| time | n + n/2 + n = 5n/2 (worst) | ≤ n, one pass |
| space | O(n) | O(1) |
| pointer | where it is at the end | its job in the stitch |
|---|---|---|
prev_left | node before the window ([1]) | prev_left.next = prev |
left_node | old front, now back of the window ([2]) | left_node.next = curr |
prev | new front of the window ([4]) | target of prev_left |
curr | first node after the window ([5] or None) | target of left_node |
2. Jump left − 1 times from the dummy → prev_left.
3. Flip exactly right − left + 1 nodes (counted loop, not
while curr).4. Stitch: prev_left.next → prev, old front.next → curr.
5. Return dummy.next.
head (wrong whenever left = 1: you get "1, 5")✗
while curr for the reversal (reverses past the window)✗ overwriting
prev_left.next before saving left_node✗ jumping
left times instead of left − 1 (you land inside the window)✗ off-by-one in the brute force: positions start at 1, indexes at 0
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def build(values):
dummy = ListNode()
tail = dummy
for v in values:
tail.next = ListNode(v)
tail = tail.next
return dummy.next
def to_list(head):
out = []
while head:
out.append(head.val)
head = head.next
return out
s = Solution()
print(to_list(s.reverseBetween(build([1, 2, 3, 4, 5, 6]), 2, 4))) # [1, 4, 3, 2, 5, 6]
print(to_list(s.reverseBetween(build([1, 2, 3, 4, 5]), 1, 4))) # [4, 3, 2, 1, 5]
print(to_list(s.reverseBetween(build([5]), 1, 1))) # [5]
print(to_list(s.reverseBetween(build([1, 2, 3]), 2, 3))) # [1, 3, 2]Based on this video: Reverse Linked List II | Reversal pattern