Introduction & Problem Explanation
The Palindrome Linked List problem asks us to determine if a given singly linked list is a palindrome (i.e., reads the same forwards and backwards).
For example, the list 1 -> 2 -> 2 -> 1 is a palindrome, while the list 1 -> 2 -> 3
is not. The primary constraint is doing this in O(n) time complexity and O(1) space
complexity. Since we cannot traverse a singly linked list backwards, this requires a clever combination of
list operations.
Imagine writing a word on a strip of paper. To check if it is a palindrome, you could fold the strip of paper exactly in half. Once folded, you compare the letter on the top page with the corresponding letter directly beneath it. For a linked list, we can perform this "fold" by finding the exact middle of the list, reversing the entire second half of the list, and then comparing the first half and the reversed second half step-by-step from their respective headers!
The Algorithmic Approach
1. Auxiliary Stack / List Approach (O(n) Space)
We can copy all list values into an ArrayList or push them onto a Stack, and then check for palindrome
properties using standard arrays/stack mechanisms. While straightforward, this requires O(n)
extra storage space, which violates the strict constant space constraint.
2. Middle Reversal Optimization (O(n) Time, O(1) Space)
We can achieve the result in-place using three main steps:
- Find the middle of the linked list using the two-pointer **fast and slow** technique (where
fastmoves twice as fast asslow). - Reverse the second half of the linked list starting from the middle node (
slow). - Compare the values of the first half (starting at
head) and the reversed second half (starting at the new head of the reversed portion) node by node.
O(n) time using only O(1) memory.
Step-by-Step Execution Walkthrough
Let's trace the algorithm on the linked list 1 -> 2 -> 2 -> 1 -> null:
- Step 1 (Find Middle):
- Initialize
slowandfastpointers athead(node1). - Iteration 1:
slow = 2(second node),fast = 2(third node). - Iteration 2:
slow = 2(third node),fast = null. - The loop terminates since
fastis null. The middle of the list is atslow(the third node2).
- Initialize
- Step 2 (Reverse Second Half):
- We call our reverse function on the second half starting at
slow:2 -> 1 -> null. - This returns the reversed chain:
1 -> 2 -> null.
- We call our reverse function on the second half starting at
- Step 3 (Compare Halves):
- Initialize
firstHalf = head(node1) andsecondHalf = reversedHead(node1). - Compare node values:
- Step 3a:
firstHalf.val (1) == secondHalf.val (1). Match. Advance pointers. - Step 3b:
firstHalf.val (2) == secondHalf.val (2). Match. Advance pointers.
- Step 3a:
- Since
secondHalfreachesnull, the comparison finishes successfully.
- Initialize
- Step 4 (Completion): All elements matched, so the method returns
true. The list is confirmed as a palindrome.
Key Code Snippets & Explanations
Here is why the main logic in the solution is important:
while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; }: Classic slow/fast algorithm. Whenfastreaches the end,slowis guaranteed to be at the middle node of the list.ListNode secondHalf = reverseList(slow);: Inverts the second half, allowing us to traverse the second half forward rather than backward.if (firstHalf.val != secondHalf.val) return false;: Immediately rejects the list as a palindrome upon detecting the first mismatch.
Java Implementation Code
Below is the complete, self-contained Java source code that solves this problem. It also includes a
main method that traces the execution with console outputs.
package io.practise.dsa;
public class PalindromeLinkedList {
public static class ListNode {
public int val;
public ListNode next;
public ListNode(int val) { this.val = val; }
}
// Optimized - Reverse 2nd Half + Compare - O(n)
public boolean isPalindrome(ListNode head) {
if (head == null || head.next == null) return true;
// Find middle
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
// Reverse second half
ListNode secondHalf = reverseList(slow);
// Compare both halves
ListNode firstHalf = head;
while (secondHalf != null) {
if (firstHalf.val != secondHalf.val) return false;
firstHalf = firstHalf.next;
secondHalf = secondHalf.next;
}
return true;
}
private ListNode reverseList(ListNode head) {
ListNode prev = null;
while (head != null) {
ListNode next = head.next;
head.next = prev;
prev = head;
head = next;
}
return prev;
}
public static void main(String[] args) {
PalindromeLinkedList solver = new PalindromeLinkedList();
ListNode head = new ListNode(1);
head.next = new ListNode(2);
head.next.next = new ListNode(2);
head.next.next.next = new ListNode(1);
System.out.println("--- Palindrome Linked List Demonstration ---");
System.out.println("Is Palindrome: " + solver.isPalindrome(head));
}
}
Conclusion
Solving the Palindrome Linked List problem highlights how we can leverage key data structures and algorithmic paradigms (like two pointers, hash maps, or dynamic programming) to significantly optimize runtime and simplify code complexity.