DSA sheet · Linked List · Reversal pattern

Palindrome Linked List

The second question of the reversal pattern. We must say whether a linked list reads the same forwards and backwards. The teacher builds the answer step by step: first the array solution with two pointers, then a brute force that copies the list into an array, and finally the optimal one that combines two patterns: fast & slow pointers to find the middle, and reversal to turn the second half around so that the "right pointer" can walk backwards. This "find middle + reverse second half + compare" recipe is reused straight away in the next problem (Maximum Twin Sum) and later in Reorder List.

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 · What you must know before starting

Nodes, head and None

A linked list is a chain of nodes. Each node holds a value (val) and a link (next) to the following node. The last node's next is None. We are only 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      # the value in this node
        self.next = next    # the following node, or None at the end
[1] → [2] → [3] → [2] → [1] → None
head

To visit the nodes we use a walker: curr = head, then curr = curr.next again and again until curr is None. We walk with a copy so that head still marks the start.

There is no index access: to reach the i-th node you must follow i arrows from the head, which costs O(i). And the arrows only go forward. You can't step back from a node to the one before it. This one fact shapes the whole solution below.

Rule for changing arrows: save next first

A node's next is the only road to the rest of the list. If you overwrite it without saving it, everything after it is lost. So we always do nxt = curr.next before changing curr.next.

Tool 1: fast & slow pointers (finding the middle)

Two walkers start at head. slow moves 1 step at a time, fast moves 2 steps. When fast reaches the end, slow has covered half the distance, so it's standing in the middle. One pass, no counting.

middle of a list (from the earlier "Middle of the Linked List" video)
slow = fast = head
while fast and fast.next:     # fast can still take 2 steps
    slow = slow.next          # 1 step
    fast = fast.next.next     # 2 steps
# slow is now the middle

Tool 2: reversing a list with prev / curr / nxt (from the previous video)

reverse the list that starts at curr; returns the new head
prev = None
while curr:
    nxt = curr.next       # save the road ahead
    curr.next = prev      # flip the arrow backward
    prev = curr           # step prev forward
    curr = nxt            # step curr forward
# prev is the new head of the reversed piece

Remember one detail: the first node we reverse ends up pointing to None (because prev starts as None). We'll need that to know when to stop comparing.

What is a palindrome?

A sequence that reads the same from both ends: 1 2 3 2 1, 1 2 2 1, 7. The first value equals the last, the second equals the second-last, and so on. We compare the values in the nodes, not the node objects.


Part A · Brute force: copy into an array, then two pointers

LeetCode 234

1The question in simple words

Given the head of a singly linked list, return True if its values form a palindrome, else False.

[1] → [2] → [3] → [2] → [1] → None     → True   (odd length)
[1] → [2] → [2] → [1] → None           → True   (even length)
[1] → [2] → None                       → False

2What the constraints tell us

3Intuition: solve it on an array first

Forget the list. On an array [1, 2, 3, 2, 1] we'd use two pointers: left at the start, right at the end. Compare the two values. If they match, move left forward and right backward, and compare again. If any pair doesn't match, it can't be a palindrome, so stop at once.

[1, 2, 3, 2, 1]
 L           R    1 = 1 ✓ → move both
    L     R       2 = 2 ✓ → move both
       LR         left == right → stop → True

4Building the logic: when do the pointers stop?

The teacher looks at both lengths:

Both cases are covered by one rule: keep going while left < right.

Early exitIf arr[left] != arr[right] at any point (say 4 against 1), return False right away. Checking the remaining pairs can't change the answer.

But we have a linked list, not an array. The right pointer would have to step backwards, and the arrows don't allow that. So the brute force simply copies the values into an array first and then runs the same two-pointer check.

(In the video she calls the list "doubly linked" for a moment. That's a slip: it's a singly linked list, and that's exactly why we can't walk backwards.)

5Approach steps

  1. Walk the list and append every value to vals.
  2. Set i = 0, j = len(vals) - 1.
  3. While i < j: if vals[i] != vals[j] return False; else i += 1, j -= 1.
  4. If the loop finishes without a mismatch, return True.

6Code (Python)

Brute force: array + two pointers
class Solution:
    def isPalindrome(self, head):
        vals = []
        curr = head
        while curr:                    # copy the values out
            vals.append(curr.val)
            curr = curr.next

        i, j = 0, len(vals) - 1        # two pointers on the array
        while i < j:                   # stop when they meet (odd) or cross (even)
            if vals[i] != vals[j]:
                return False           # one mismatch is enough
            i += 1
            j -= 1
        return True

7Code line by line

linewhat it means
vals = []The extra array. This costs O(n) space.
while curr: vals.append(curr.val) curr = curr.nextOne pass over the list, copying each value.
i, j = 0, len(vals) - 1Left pointer on the first index, right pointer on the last index (length − 1).
while i < j:Odd length ends with i == j; even length ends with i > j. Either way, all pairs are checked.
if vals[i] != vals[j]: return FalseA pair doesn't match → not a palindrome. Stop now.
i += 1 j -= 1Move both pointers one step towards the middle, in the same round.
return TrueReached only if no pair failed.

8Dry run

stepvalsi, jcompareresult
0[1, 2, 3, 2, 1]–copied from the list
10, 41 vs 1equal → move
21, 32 vs 2equal → move
32, 2i < j is falsestop → True

With [1, 2, 3, 4, 1]: step 1 compares 1 vs 1 ✓, step 2 compares 2 vs 4 → False, and we never look at the middle.

9Complexity & remember

Remember the brute forceCopy to an array, then two pointers with while i < j. Return False on the first mismatch. O(n) extra space is the weakness.

Part B · Optimal: middle → reverse second half → compare

1The question (same), with a new goal

Same input and output, but now with O(1) extra space: no array.

2What the constraints tell us

3Intuition: make the right pointer able to walk backwards

The teacher goes through the patterns one by one to pick a tool:

before:              [1] → [2] → [3] → [2] → [1] → None
second half reversed:[1] → [2] → [3] ← [2] ← [1]
                     head1                   head2
Now head1 walks right and head2 walks left, both with .next

4Building the logic step by step

Step 1: find the middle with fast & slow

Odd example 1 2 3 2 1:

start
[1] → [2] → [3] → [2] → [1] → None
slow/fast

move 1
[1] → [2] → [3] → [2] → [1] → None
      slow  fast

move 2
[1] → [2] → [3] → [2] → [1] → None
            slow        fast

stop: fast.next is None → middle = slow = [3] (position 2)

Even example 1 2 2 1:

start
[1] → [2] → [2] → [1] → None
slow/fast

move 1
[1] → [2] → [2] → [1] → None
      slow  fast

move 2
[1] → [2] → [2] → [1] → None
            slow        fast

stop: fast is None → middle = slow = [2] (position 2)

In both cases slow ends on the node where the second half starts (for odd length, the middle node itself).

Step 2: reverse from slow to the end

Keep head where it is: if we moved it, we'd lose the first half. Reverse the piece that starts at slow, using the four lines from the previous video. For the odd example, the piece is [3] → [2] → [1]:

start: prev = None, curr = slow
None   [3] → [2] → [1] → None
prev   curr

round 1: nxt = curr.next, curr.next = prev (red), then move
None ← [3]   [2] → [1] → None
prev   curr  nxt
       prev  curr   ← after moving

round 2: nxt = curr.next, curr.next = prev (red), then move
None ← [3] ← [2]   [1] → None
       prev  curr  nxt
             prev  curr   ← after moving

round 3: nxt = curr.next, curr.next = prev (red), then move
None ← [3] ← [2] ← [1]   None
             prev  curr  nxt
                   prev  curr   ← after moving

Now prev is on the last node, the head of the reversed half. The teacher calls it head2, and the original head head1. The whole picture:

[1] → [2] → [3] ← [2] ← [1]
head1        ↓          head2 (= prev)
            None
head1's walk: 1, 2, 3, then None     head2's walk: 1, 2, 3, then None
Doubt 1: the first [2] still points to [3]. Isn't the list broken?
→ Nothing was lost. The first half still ends at [3], because we never touched [2]'s arrow. And [3] was the first node we reversed, so it now points to None (prev started as None). Both walks end at [3] and then None. The middle node simply belongs to both halves, and comparing it with itself is harmless.
Doubt 2: in the video's drawing, head2 has 2 nodes and head1 has 3. Here both have 3. Which is right?
→ It depends on where you start reversing. Her drawing reverses only the nodes after the middle. Her code (and ours) reverses from slow itself, so the middle node is shared. Both work: in the odd case head2's piece is never longer than head1's, and in the even case the pieces are equal. That's why we loop on head2.

Step 3: compare, walking both pointers forward

5Approach steps

  1. slow = fast = head. While fast and fast.next: slow 1 step, fast 2 steps.
  2. Reverse the list starting at slow with prev / curr / nxt. The new head is prev.
  3. left = head, right = prev.
  4. While right: if the values differ → False; else move both one step.
  5. Return True.

6Code (Python)

Optimal: fast & slow + reverse second half, O(1) space
class Solution:
    def isPalindrome(self, head):
        # 1. find the middle
        slow = fast = head
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next

        # 2. reverse the second half (starting at slow)
        prev = None
        curr = slow
        while curr:
            nxt = curr.next
            curr.next = prev
            prev = curr
            curr = nxt

        # 3. compare the first half with the reversed second half
        left, right = head, prev        # head1 and head2
        while right:
            if left.val != right.val:
                return False
            left = left.next
            right = right.next
        return True

7Code line by line

linewhat it means
slow = fast = headBoth walkers start at the first node. Safe, because the list has at least 1 node.
while fast and fast.next:Stop when fast falls off (even length) or sits on the last node (odd length).
slow = slow.next fast = fast.next.nextSlow moves 1, fast moves 2. When the loop ends, slow is at the start of the second half.
prev = None curr = slowSet up the reversal of the piece from slow to the end. The first reversed node will point to None.
nxt = curr.next … curr = nxtThe standard four lines: save, flip, move prev, move curr.
left, right = head, prevhead1 at the very start, head2 at the old last node (start of the reversed half).
while right:head2's piece is the shorter one or equal, so it controls the loop.
if left.val != right.val: return FalseA matching pair from the two ends is different → not a palindrome.
left = left.next right = right.nextLeft moves towards the middle from the front; right moves towards the middle from the back (thanks to the reversed arrows).
return TrueAll pairs matched.

8Dry runs

Odd: 1 → 2 → 3 → 2 → 1

After steps 1 and 2 (drawn above): head1 = [1] (first), head2 = [1] (last).

roundpointerscomparewhat changesanswer so far
1left = 1st node, right = 5th node1 vs 1both movestill True
2left = 2nd node, right = 4th node2 vs 2both movestill True
3left = 3rd node, right = 3rd node (the same node)3 vs 3left → None, right → Nonestill True
4right is None–loop endsreturn True

Only 3 rounds for 5 nodes, which matches the teacher's count.

Even: 1 → 2 → 2 → 1

slow ends on the 3rd node (the second 2). Reversing [2] → [1] gives [1] → [2] → None:

[1] → [2] → [2] ← [1]
head1        ↓    head2
            None
head1's walk: 1, 2, 2   head2's walk: 1, 2
roundpointerscomparewhat changesanswer so far
1left = 1st, right = 4th1 vs 1both movestill True
2left = 2nd, right = 3rd2 vs 2right → Nonestill True
3right is None–loop endsreturn True

Here head1's piece is longer (it still reaches the second 2), and that's fine: we stop on head2.

Not a palindrome: 1 → 2 → 3 → 4 → 1

slow ends on [3]. Reversing [3] → [4] → [1] gives head2 = [1] → [4] → [3] → None. Round 1: 1 vs 1 ✓. Round 2: 2 vs 4 → return False.

Doubt 3: our function changed the input list. Is that OK?
→ LeetCode doesn't check, so the teacher leaves it. In an interview, it's good manners to mention it and, if asked, reverse the second half again (reverse(prev)) before returning, to put the list back. That costs one more n/2 pass and keeps O(1) space. (This point is mine, not from the video.)

9Complexity & remember

The teacher says the brute force was "2n" when she compares at the end, but by her own counting earlier it's also n + n/2 = 3n/2. So the time is about the same. The real win is space: O(n) → O(1). On LeetCode the optimal version also ran faster, since it doesn't build an array.

Remember Palindrome List Fast & slow to the middle → reverse from slow → compare head with prev while prev exists. The second half's arrows point back, so the "right pointer" can walk with .next.

Part C · Revision page

Brute forceOptimal
ideacopy to array, two pointers from both endsreverse the second half so the right pointer can move with .next
patterns usedtwo pointers on an arrayfast & slow + reversal + two pointers
stop conditionwhile i < jwhile right (the reversed half ends in None)
changes the list?noyes (second half reversed; can be undone)
timeO(n + n/2)O(n/2 + n/2 + n/2)
spaceO(n)O(1)
lengthwhere slow stopshead1's walkhead2's walk
odd (1 2 3 2 1)exact middle [3]1, 2, 31, 2, 3
even (1 2 2 1)second middle (the second 2)1, 2, 21, 2
If you remember only 5 lines 1. Palindrome = 1st equals last, 2nd equals second-last, …
2. Array: two pointers, while i < j, return False on the first mismatch.
3. A list can't walk backwards, so reverse the second half.
4. Find that half with slow (1 step) / fast (2 steps), while fast and fast.next.
5. Compare head vs prev while prev isn't None. O(n) time, O(1) space.
Mistakes to avoid ✗ moving head while finding the middle (you lose the first half)
✗ while fast.next and fast (checks fast.next before fast → crash on even length)
✗ looping the comparison on head1 instead of head2 (for even length head1's walk is one node longer, so head2 becomes None first and right.val crashes)
✗ comparing nodes (left != right) instead of values (left.val != right.val)
✗ forgetting the early return False and checking everything anyway
test it yourself (paste under any of the solutions 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

s = Solution()
print(s.isPalindrome(build([1, 2, 3, 2, 1])))   # True
print(s.isPalindrome(build([1, 2, 2, 1])))      # True
print(s.isPalindrome(build([1, 2])))            # False
print(s.isPalindrome(build([7])))               # True
print(s.isPalindrome(build([1, 2, 3, 4, 1])))   # False

Based on this video: Palindrome Linked List | Reversal pattern