DSA sheet · Linked List · Reversal pattern (circular list idea)

Rotate List

This problem sits in the reversal section of the sheet, but the teacher points out straight away that no reversing is needed. The real tool is a circular linked list: join the tail to the head to make a ring, then cut the ring at the right place. Before that, she teaches an important habit: when k can be huge (up to 2·10⁹) but the list is tiny (≤ 500 nodes), use modulo to throw away the useless full rotations. She also shows a brute force that copies the values into arrays, so we can see why the in-place version is better.

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 · Before starting

Linked list basics

A linked list is a chain of nodes. Each node has a val and a next pointer to the node after it. The last node (the tail) has next = None. We are given the first node, the head.

given by LeetCode, don't write this in the solution
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next
[1] → [2] → [3] → [4] → [5] → None
 ↑                         ↑
head                      tail

Circular linked list

If the tail's next points back to head instead of None, the chain becomes a ring: you can walk round forever. There's no "first" or "last" node in a ring. You choose where it starts by where you cut it.

[1] → [2] → [3] → [4] → [5]
 ↑                         │
 └─────────────────────────┘     (tail.next = head)

Modulo (%)

a % b is the remainder when a is divided by b. Example: 6 % 5 = 1, 11 % 5 = 1, 16 % 5 = 1, 10 % 5 = 0. It's the tool for "going round in circles": after every b steps you are back where you started, so only the remainder matters.

Part A · Brute force: copy values, rotate in an array, write back

LeetCode 61

1The question in simple words

You get the head of a linked list and a number k. Rotate the list to the right by k places and return the new head. One rotation means: take the last node off and put it at the front.

start:         [1] → [2] → [3] → [4] → [5] → None
1 rotation:    [5] → [1] → [2] → [3] → [4] → None
2 rotations:   [4] → [5] → [1] → [2] → [3] → None    ← answer for k = 2

2What the constraints tell us

3Intuition: rotations go round in a circle

The teacher rotates 1 2 3 4 5 six times:

rotationslist
01 2 3 4 5
15 1 2 3 4
24 5 1 2 3
33 4 5 1 2
42 3 4 5 1
51 2 3 4 5 (back to the start!)
65 1 2 3 4 (same as 1 rotation)

After n rotations (n = length) the list is exactly as it was. So k = 1, 6, 11, 16 … all give the same answer as k = 1, because 6 % 5 = 11 % 5 = 16 % 5 = 1. Only k % n rotations really matter. After this, k is at most n − 1 ≤ 499, tiny.

And if k % n == 0 (k is a multiple of the length, e.g. 5 rotations of 5 nodes), nothing changes at all: return head as it is.

4Building the logic: the array formula

The teacher reuses the array-rotation idea from the arrays topic. Copy the values into a Python list, rotate them into a second list, and write them back into the nodes.

Where does each value go after k right rotations? The value at index i moves k places to the right. If that runs off the end, it wraps round to the front. So:

Formularotated[(i + k) % n] = vals[i]

Example: vals = [1, 2, 3, 4, 5], k = 2, n = 5:

ivals[i](i + 2) % 5rotated after this step
012[_, _, 1, _, _]
123[_, _, 1, 2, _]
234[_, _, 1, 2, 3]
345 % 5 = 0 (wraps)[4, _, 1, 2, 3]
456 % 5 = 1 (wraps)[4, 5, 1, 2, 3]

Then walk the list again and overwrite each node's value from rotated: the first node gets 4, then 5, 1, 2, 3. Return the same head.

Doubt 1: why do we need the second list? Can't we write straight into vals?
→ Writing vals[(i + k) % n] = vals[i] in place would overwrite values we haven't moved yet (e.g. putting 1 into index 2 destroys the 3 before it's moved). A separate rotated list keeps all the originals safe.
Doubt 2: is this really "rotating the linked list"?
→ Not really. The nodes stay in the same order; only their values are moved around. The teacher points this out: it's not an in-place rotation of the list. It passes on LeetCode (the output only shows values), but an interviewer will want the nodes themselves rearranged, with no extra arrays.

5Approach steps

  1. If head is None or head.next is None → return head (nothing to rotate).
  2. Walk the list and copy every value into vals. n = len(vals).
  3. k = k % n. If k is 0 → return head.
  4. Make rotated of size n; for each i: rotated[(i + k) % n] = vals[i].
  5. Walk the list again, writing rotated[0], rotated[1], … into the nodes.
  6. Return head.

6Code (Python)

Rotate List, brute force with two arrays
class Solution:
    def rotateRight(self, head, k):
        if head is None or head.next is None:   # 0 or 1 node
            return head

        vals = []
        curr = head
        while curr is not None:                 # copy values out
            vals.append(curr.val)
            curr = curr.next

        n = len(vals)
        k = k % n                               # drop full circles
        if k == 0:
            return head

        rotated = [0] * n
        for i in range(n):
            rotated[(i + k) % n] = vals[i]      # move each value k to the right

        curr = head
        i = 0
        while curr is not None:                 # write values back
            curr.val = rotated[i]
            i += 1
            curr = curr.next
        return head

7Code line by line

linewhat it means
if head is None or head.next is None: return headBase case. Empty list (allowed by the constraints) or one node: rotation changes nothing. Also protects k % n from dividing by 0.
vals.append(curr.val)Pass 1: read every value into a Python list.
k = k % nThrow away whole circles. A k of 2·10⁹ becomes at most n − 1.
if k == 0: return headk was a multiple of n, so the list ends up unchanged.
rotated[(i + k) % n] = vals[i]Pass 2: each value moves k places right; % n wraps it round to the front if needed.
curr.val = rotated[i]Pass 3: overwrite the node values in order.
return headSame first node, now holding the first rotated value.

8Dry run

1 → 2 → 3 → 4 → 5, k = 2.

steppointers / datawhat changeslist after this stepanswer so far
copycurr walks 1 … 5vals = [1, 2, 3, 4, 5], n = 51 → 2 → 3 → 4 → 5—
modulo—k = 2 % 5 = 2 (not 0)unchanged—
rotatei = 0 … 4rotated = [4, 5, 1, 2, 3] (table above)unchanged—
writecurr walks againnode values become 4, 5, 1, 2, 34 → 5 → 1 → 2 → 3return head (now holds 4)

k = 7 on the same list gives 7 % 5 = 2, the same answer. k = 5 gives 0 → the list is returned untouched.

9Complexity & remember

Remember brute forceBase case 0/1 node · copy values · k %= n, 0 → return · rotated[(i + k) % n] = vals[i] · write back. Works, but moves values, not nodes, and uses O(n) space.

Part B · Optimal: make a ring, then cut it (in place)

1The question (same as Part A)

Same input and output, but now we want to rearrange the nodes themselves, with no extra arrays: O(1) extra space.

2What the constraints tell us

3Intuition: a necklace

Look at the answer for k = 2: 4 5 1 2 3. It's the same order as 1 2 3 4 5 if you read it round a circle, just starting from 4. So:

  1. Join the tail (5) to the head (1). Now it's a necklace: 1 2 3 4 5 1 2 3 …
  2. Cut the necklace just before 4. The piece starts at 4 and ends at 3: 4 → 5 → 1 → 2 → 3 → None.

Only two pointers change: the old tail's next (5 → 1) and the new tail's next (3 → None). The teacher says when you see it, you'll be amazed how simple it is.

4Building the logic from examples

Step 1: find the length and the tail in one walk

Start curr on head with length = 1 (we are already standing on one node). Move while curr.next is not None, adding 1 each time. When the loop stops, curr is on the tail and length is the true count.

Doubt 1: why start length at 1 and loop on curr.next, not curr?
→ We need curr to stop on the tail (to link it to head later). Looping on curr would walk off the end into None. Since we stand on the head before the first move and never count it inside the loop, we start the counter at 1. (Same lesson as Nth from End: the starting value and the loop condition must agree.)

Step 2: shrink k, and stop early if nothing changes

k = k % length. With 5 nodes and k = 2 (or 7, or 2·10⁹ + 2…), k becomes the real number of rotations. If it is 0, return head right away.

Doubt 2: why check k == 0 before joining the tail to the head?
→ If we joined first and then returned head, we'd hand back a ring (an infinite loop). Returning early keeps the list as it was. (If you join first, you must still cut somewhere, and with k = 0 the cut is at the old tail. The early return is simpler.)

Step 3: join tail to head

curr.next = head. curr is on 5, so 5 now points to 1. The pointer head itself still sits on 1; we haven't moved it.

Step 4: how many jumps to the new tail? length − k − 1

After k right rotations, the last k nodes move to the front. So the new head is the node at position length − k (counting from 0), and the new tail is the one just before it.

Doubt 3: why stop one node before the new head?
→ To cut the ring we have to change the next of the node before the new head (it must become None). From the new head we can't reach back to it. It's the same "stand before the node" rule as deleting a node.
Doubt 4: is this related to the previous problems?
→ Yes. The new head is the k-th node from the end, exactly what Nth from End finds. Here we already know the length (we needed it for the modulo), so we simply walk length − k − 1 jumps instead of using a gap.

Step 5: save the new head, then cut

Standing on the new tail (3): new_head = new_tail.next (that's 4), then new_tail.next = None. Return new_head.

Doubt 5: why save new_head before setting next to None?
→ new_tail.next is our only handle on node 4 right now. If we set it to None first, we lose 4 (and the way into the rest of the ring). Save next before changing a pointer.

5Approach steps

  1. If head is None, or head.next is None, or k == 0 → return head.
  2. Walk to the tail, counting nodes (start length at 1).
  3. k %= length; if k == 0 → return head.
  4. tail.next = head (make a ring).
  5. From head, take length − k − 1 jumps → this is the new tail.
  6. new_head = new_tail.next, then new_tail.next = None. Return new_head.

6Code (Python)

Rotate List, ring and cut (in place)
class Solution:
    def rotateRight(self, head, k):
        if head is None or head.next is None or k == 0:
            return head

        curr = head
        length = 1                       # we're standing on head already
        while curr.next is not None:     # stop ON the tail
            curr = curr.next
            length += 1

        k = k % length
        if k == 0:                       # full circles only: no change
            return head

        curr.next = head                 # tail -> head: a ring

        new_tail = head
        for _ in range(length - k - 1):  # walk to the node before the new head
            new_tail = new_tail.next

        new_head = new_tail.next         # save it before cutting
        new_tail.next = None             # cut the ring
        return new_head

7Code line by line

linewhat it means
if head is None or head.next is None or k == 0: return headEmpty list, one node, or no rotation: the answer is the input.
curr = head length = 1Start on the first node, which already counts as 1.
while curr.next is not None: curr = curr.next length += 1Walk to the last node, counting. Afterwards curr = tail, length = n.
k = k % lengthRemove useless full rotations (k up to 2·10⁹ becomes < n).
if k == 0: return headRotating by a multiple of n changes nothing. Return before making the ring.
curr.next = headThe tail now points to the head: the list is a ring.
for _ in range(length - k - 1): new_tail = new_tail.nextWalk from head to the node that will become the last one.
new_head = new_tail.nextThe node after the new tail is the new first node. Save it now.
new_tail.next = NoneCut the ring here: this node is now the end.
return new_headThe rotated list, made of the very same nodes.

8Dry run

1 → 2 → 3 → 4 → 5, k = 2.

steppointerswhat changeslist picture after this stepanswer so far
1curr walks 1 → 5length = 5, curr = tail (5)1 → 2 → 3 → 4 → 5 → None—
2—k = 2 % 5 = 2unchanged—
3curr = 55.next: None → 1ring 1 → 2 → 3 → 4 → 5 → 1 …—
4new_tail: 1 → 2 → 35 − 2 − 1 = 2 jumpsring (unchanged)—
5new_tail = 3new_head = 4 (saved)ring (unchanged)—
6new_tail = 33.next: 4 → None4 → 5 → 1 → 2 → 3 → Nonereturn 4
after step 1 (found the tail):
[1] → [2] → [3] → [4] → [5] → None
 ↑                         ↑
head                      curr

after step 3 (ring made):
[1] → [2] → [3] → [4] → [5]
 ↑                         │
 └─────────────────────────┘

after step 5 (standing before the new head):
[1] → [2] → [3] → [4] → [5]
 ↑           ↑     ↑       │
 │      new_tail  new_head │
 └─────────────────────────┘

after step 6 (cut):
[4] → [5] → [1] → [2] → [3] → None
 ↑                         ↑
new_head               new_tail

Other cases

9Complexity & remember

Remember ring and cutLength + tail (start count at 1, loop on curr.next) · k %= length, 0 → return · tail.next = head · jump length − k − 1 from head · save new_head = new_tail.next · new_tail.next = None.

Part C · Revision page

Arrays (brute force)Ring and cut (optimal)
ideacopy values, rotated[(i + k) % n] = vals[i], write backlink tail to head, cut before the new head
what movesvalues (nodes stay put)nodes (only 2 next pointers change)
modulok %= n; if 0 → return head (k can be 2·10⁹)
base casehead is None or head.next is None → return head
time / spaceO(3n) / O(2n)O(2n) / O(1)
If you remember only 5 lines 1. A right rotation moves the last node to the front; n rotations bring the list back.
2. So use k %= n; if it's 0, return head.
3. Find the length and the tail in one walk (count from 1, stop on the tail).
4. Join tail → head, walk length − k − 1 jumps from head to the new tail.
5. Save new_head = new_tail.next, then new_tail.next = None. O(n) time, O(1) space.
Mistakes to avoid ✗ rotating k times one by one (k up to 2·10⁹ → TLE)
✗ forgetting the empty-list base case (k % 0 crashes)
✗ returning early after making the ring (you return an infinite loop)
✗ walking length − k jumps (lands on the new head, not before it)
✗ cutting new_tail.next before saving the new head
test it yourself (paste under either solution above)
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.rotateRight(build([1, 2, 3, 4, 5]), 2)))       # [4, 5, 1, 2, 3]
print(to_list(s.rotateRight(build([0, 1, 2]), 4)))             # [2, 0, 1]
print(to_list(s.rotateRight(build([1, 2, 3, 4, 5]), 5)))       # [1, 2, 3, 4, 5]
print(to_list(s.rotateRight(build([1, 2, 3, 4, 5]), 2 * 10**9 + 2)))  # [4, 5, 1, 2, 3]
print(to_list(s.rotateRight(build([]), 3)))                    # []

Based on this video: Rotate List | Circular Linked List