Problem
Given a singly linked list, determine whether its values read the same forwards and backwards. You cannot simply index into it β you must traverse it with pointers.
[1,2,2,1]
- Input: head = [1, 2, 2, 1]
- Output: true
- Explanation: The sequence 1 β 2 β 2 β 1 is identical whether read left-to-right or right-to-left.
Counter-example:
[1,2,3,1]
- Input: head = [1, 2, 3, 1]
- Output: false
- Explanation: 1 β 2 β 3 β 1 reversed is 1 β 3 β 2 β 1, which differs at the inner elements.
Intuition
The simplest way to check a palindrome is to collect all values and use two pointers converging from both ends. The insight behind the O(1)-space approach is that you do not need to store anything β you can reverse the second half of the list in-place and then walk both halves together, comparing node by node.
Approach 1 β Copy to Array
Collect every value into a list, then use left/right pointers to verify symmetry. Simple to reason about but costs O(n) extra memory.
- Walk the list from head to tail, appending each value to an array.
- Set
left = 0andright = len - 1. - While
left < right, comparevalues[left]andvalues[right]; returnfalseon any mismatch. - Advance
leftforward andrightbackward after each comparison. - Return
trueif no mismatch was found.
1def isPalindrome(self, head: Optional[ListNode]) -> bool:
2 values = []
3 current = head
4 while current:
5 values.append(current.val)
6 current = current.next
7
8 left, right = 0, len(values) - 1
9 while left < right:
10 if values[left] != values[right]:
11 return False
12 left += 1
13 right -= 1
14 return TrueTime: O(n) β single pass to collect, single pass to compare.
Space: O(n) β stores all n values in the array.
Approach 2 β Reverse Second Half In-Place
Find the midpoint with slow/fast pointers, reverse the second half in-place, then compare the two halves node by node. No extra memory needed beyond a few pointers.
- Use slow and fast pointers β slow advances one step, fast two β stopping when
fast.nextorfast.next.nextisNone. Slow then sits at the last node of the first half. - Reverse the sublist beginning at
slow.next, producing a reversed second half whose head is the original tail. - Walk
firstfromheadandsecondfrom the reversed second-half head simultaneously, comparing values. - Return
falseon the first mismatch,trueifsecondis exhausted without one.
1def isPalindrome(self, head: Optional[ListNode]) -> bool:
2 slow, fast = head, head
3 # Stop when slow is at the last node of the first half
4 while fast.next and fast.next.next:
5 slow = slow.next
6 fast = fast.next.next
7
8 # Reverse from slow.next onward
9 prev, current = None, slow.next
10 while current:
11 next_node = current.next
12 current.next = prev
13 prev = current
14 current = next_node
15 second_half_head = prev # now points to the original tail
16
17 # Compare first half (headβ¦slow) with reversed second half
18 first, second = head, second_half_head
19 while second:
20 if first.val != second.val:
21 return False
22 first = first.next
23 second = second.next
24 return TrueTime: O(n) β three linear passes (find mid, reverse, compare).
Space: O(1) β only a constant number of pointer variables.
Complexity Summary
| Approach | Time | Space | When to use |
|---|---|---|---|
| Copy to Array | O(n) | O(n) | When clarity matters more than memory, or the list must not be mutated |
| Reverse Second Half | O(n) | O(1) | Whenever you need constant extra memory and can tolerate temporarily modifying the list |
Common Mistakes
- Using
while fast and fast.nextinstead ofwhile fast.next and fast.next.nextβ the wrong condition letsslowadvance one step too far on even-length lists, so the second half contains only the last element and misses inner comparisons.[1,2,3,1]would incorrectly returntruebecause only1 == 1is checked. - Starting the reversal from
slowrather thanslow.nextβ when the list has odd length,slowsits on the true middle node; reversing fromslowpulls the middle into the second half and causes an off-by-one mismatch during comparison. - Comparing
second.next != nullas the loop condition β this skips the last node of the second half entirely; the correct guard iswhile secondso every reversed node is checked. - Forgetting that the second half is shorter or equal, never longer β when iterating, always drive the loop with
second(the shorter or equal-length half), notfirst, otherwisefirst.nextmay benullbeforesecondis exhausted and you'll get a null-pointer crash. - Leaving the list permanently reversed β in systems where the structure must be preserved after the check, you need to reverse the second half back again (
slow.next = reverse(secondHalfHead)); omitting this silently corrupts the list for any caller that reads it afterward.
Related Problems
reverse-linked-listβ the in-place reversal used in Approach 2 is exactly this subroutinemiddle-of-the-linked-listβ isolates the slow/fast midpoint-finding step that Approach 2 relies onreorder-listβ also combines find-middle and reverse-second-half before interleaving the two halvesvalid-palindromeβ the same two-pointer palindrome check applied to a string instead of a linked listpalindrome-numberβ palindrome verification without converting to a string, analogous to checking without extra space