EasyLinked List

Palindrome Linked List β€” Solution

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
β†’βˆ…
[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
β†’βˆ…
[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.

  1. Walk the list from head to tail, appending each value to an array.
  2. Set left = 0 and right = len - 1.
  3. While left < right, compare values[left] and values[right]; return false on any mismatch.
  4. Advance left forward and right backward after each comparison.
  5. Return true if 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 True

Time: 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.

  1. Use slow and fast pointers β€” slow advances one step, fast two β€” stopping when fast.next or fast.next.next is None. Slow then sits at the last node of the first half.
  2. Reverse the sublist beginning at slow.next, producing a reversed second half whose head is the original tail.
  3. Walk first from head and second from the reversed second-half head simultaneously, comparing values.
  4. Return false on the first mismatch, true if second is 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 True

Time: O(n) β€” three linear passes (find mid, reverse, compare).
Space: O(1) β€” only a constant number of pointer variables.

Complexity Summary

ApproachTimeSpaceWhen to use
Copy to ArrayO(n)O(n)When clarity matters more than memory, or the list must not be mutated
Reverse Second HalfO(n)O(1)Whenever you need constant extra memory and can tolerate temporarily modifying the list

Common Mistakes

  • Using while fast and fast.next instead of while fast.next and fast.next.next β€” the wrong condition lets slow advance 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 return true because only 1 == 1 is checked.
  • Starting the reversal from slow rather than slow.next β€” when the list has odd length, slow sits on the true middle node; reversing from slow pulls the middle into the second half and causes an off-by-one mismatch during comparison.
  • Comparing second.next != null as the loop condition β€” this skips the last node of the second half entirely; the correct guard is while second so 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), not first, otherwise first.next may be null before second is 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 subroutine
  • middle-of-the-linked-list β€” isolates the slow/fast midpoint-finding step that Approach 2 relies on
  • reorder-list β€” also combines find-middle and reverse-second-half before interleaving the two halves
  • valid-palindrome β€” the same two-pointer palindrome check applied to a string instead of a linked list
  • palindrome-number β€” palindrome verification without converting to a string, analogous to checking without extra space

Ready to practice? Try it on SkillFlow

Adaptive problems, AI follow-up interviews, and a skill score that shows exactly where you need to improve.

Practice This Problem β†’