Topics › Linked List › Medium Problems of LL
Check if LL is palindrome or not
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
You are given the head of a singly linked list. Return true if the sequence of values reads the same from front to back as from back to front, and false otherwise. An empty list and a single node are palindromes. The list should be left exactly as it was when the function returns.
Constraints: 0 ≤ n ≤ 10^5 nodes, values fit in a 32-bit signed integer. The follow-up asks for O(n) time and O(1) extra space.
static class ListNode {
int val;
ListNode next;
ListNode(int val) { this.val = val; }
ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}
Example 1
head = 1 -> 2 -> 2 -> 1
answer = true // pairs (1,1) and (2,2) match
Example 2
head = 1 -> 2 -> 3 -> 2 -> 1
answer = true // odd length: the middle 3 pairs with itself
Example 3
head = 1 -> 2
answer = false // 1 != 2
Approach 1: Brute force, copy to a stack
Intuition. A singly linked list can only be read forwards. If we copy all the values onto a stack, popping them gives the values backwards. Walk the list a second time and compare each node’s value with the popped value; the first mismatch proves the list is not a palindrome.
Algorithm.
- Walk from
headand push every value onto a stack. - Walk from
headagain. For each node, pop the stack; if the values differ, returnfalse. - Return
truewhen the second walk finishes.
static boolean bruteForce(ListNode head) {
Deque<Integer> stack = new ArrayDeque<>();
for (ListNode t = head; t != null; t = t.next) stack.push(t.val);
for (ListNode t = head; t != null; t = t.next) {
if (t.val != stack.pop()) return false; // the pop yields the values back to front
}
return true;
}
Complexity. Time O(n): two passes with constant work per node. Space O(n): the stack holds all n values.
Copying into an ArrayList and comparing with two indices from both ends is an equivalent alternative. Both are fine answers, but the interesting part of the problem is doing it without a copy.
Approach 2: Optimal, reverse the second half in place
Intuition. A palindrome’s second half is the mirror image of its first half. We cannot read the second half backwards directly, but we can reverse it in place and then read it forwards, which is the same thing. So: find the middle, reverse the part after the middle, and walk the two halves together comparing values. Afterwards, reverse the second half again so the caller’s list is unchanged.
The middle is found with slow and fast pointers. We want slow to stop on the last node of the first half, so that slow.next is the first node of the second half. Using the condition fast.next != null && fast.next.next != null achieves exactly that: for an even length 2k, slow ends on node k; for an odd length 2k + 1, slow ends on the middle node k + 1, which is then left out of the comparison (a middle element always matches itself).
Algorithm.
- If the list has fewer than two nodes, return
true. - Set
slow = head,fast = head. Whilefast.nextandfast.next.nextare non-null, moveslowby one andfastby two. - Let
secondHalf = reverse(slow.next). - Walk
afromheadandbfromsecondHalfwhilebis non-null; ifa.val != b.val, note the failure and stop. - Restore:
slow.next = reverse(secondHalf). - Return the result of the comparison.
static boolean optimal(ListNode head) {
if (head == null || head.next == null) return true;
ListNode slow = head, fast = head;
while (fast.next != null && fast.next.next != null) { // slow stops on the last node of the first half
slow = slow.next;
fast = fast.next.next;
}
ListNode secondHalf = reverse(slow.next); // second half, now reversed
boolean palindrome = true;
ListNode a = head, b = secondHalf;
while (b != null) { // the second half is never longer than the first
if (a.val != b.val) { palindrome = false; break; }
a = a.next;
b = b.next;
}
slow.next = reverse(secondHalf); // put the list back the way it was
return palindrome;
}
The reversal is the standard three-pointer routine:
static ListNode reverse(ListNode head) {
ListNode prev = null, current = head;
while (current != null) {
ListNode next = current.next;
current.next = prev;
prev = current;
current = next;
}
return prev;
}
Dry run on 1 -> 2 -> 2 -> 1:
| step | state |
|---|---|
| find middle | slow stops on the second node (value 2); fast on the third |
| reverse | second half 2 -> 1 becomes 1 -> 2; secondHalf is the old tail |
| compare | a=1, b=1 ok; a=2, b=2 ok; b becomes null, stop |
| restore | 1 -> 2 reversed back to 2 -> 1 and reattached after slow |
| result | true |
On 1 -> 2 -> 3 -> 2 -> 1, slow stops on the 3; the second half 2 -> 1 is reversed to 1 -> 2 and compared against 1 -> 2 from the head. The 3 is never compared, which is correct.
Complexity. Time O(n): finding the middle, reversing, comparing and restoring are each a single pass over at most n nodes. Space O(1): a handful of pointers; nothing is copied.
The Java test checks, after every call to optimal, that toArray(head) still equals the original input and that calling optimal a second time on the restored list gives the same answer. This matters most in the early-exit case (2 -> 1 -> 1 -> 2 -> 1), where the comparison stops after the first mismatch but the restoration must still happen.
Edge cases and pitfalls
- Empty list and single node. Both are palindromes; the optimal method returns
truebefore touching any pointer. - Two nodes.
slowstays on the head, the second half is the single second node, and one comparison decides. - Odd length. The middle node belongs to the first half and is skipped by
while (b != null), because the reversed second half is one node shorter. - Forgetting to restore. Leaving the second half reversed corrupts the caller’s list. Reverse it back before returning, including on the
falsepath; abreakout of the comparison loop followed by the restore line is the simple way to guarantee it. - Comparing with the wrong loop bound. Iterate while the second-half pointer
bis non-null, not whileais non-null;ahas more nodes ahead of it. - Wrong middle. The condition
fast != null && fast.next != nullmovesslowone node too far for even lengths (onto the first node of the second half). The method usesfast.next != null && fast.next.next != nullso thatslowis the last node of the first half. - Values, not identity. This problem compares values with
!=onint, unlike loop detection problems where node identity is compared.
Related problems
- Reverse a LL is the in-place reversal reused here.
- Middle of a LinkedList [TortoiseHare Method] explains the slow and fast pointer trick for finding the middle.
- Reverse words in a given string / Palindrome Check checks a palindrome on a string with two indices.
- Palindrome Number is the same idea on the digits of an integer.