Given the head of a singly linked list, determine if it's a palindrome.
Output: True
Output: False
New to this one? Start here. Below is the core idea in everyday language, plus a real-world analogy—then the sections that follow build on it with the formal reasoning, code, and step-by-step walkthroughs.
Write the numbers along a paper strip, then fold it in half. If every number lands exactly on its mirror twin, the strip is a palindrome. We can't literally fold a linked list, but we can do the next best thing: reverse the back half so it lines up head-to-head with the front half, then check the two halves match number by number.
A linked list would be palindromic if its values read the same forward and backward. A naive way to check this would be to store all the values of the linked list in an array, allowing us to freely traverse these values forward and backward to confirm if it’s palindromic. However, this takes linear space. Instead, it would be better if we had a way to traverse the linked list in reverse order to confirm if it's a palindrome. Is there a way to go about this?
Going off the above definition, we know that if a linked list is a palindrome, reversing it would result in the same sequence of values.
This means we could create a copy of the linked list, reverse it, and compare its values with the original linked list. However, this would still take up linear space. Can we adjust this idea to avoid creating a new linked list?
An important observation is that we only need to compare the first half of the original linked list with the reverse of the second half (if there are an odd number of elements, we can just include the middle node in both halves) to check if the linked list is a palindrome:
Before we can perform this comparison, we need to:
Notice that step 2 involves modifying the input. In this problem, let’s assume this is acceptable. However, it's always good to check with the interviewer if changing the input is allowed before moving forward with the solution.
Now, let’s see how these two steps can be applied. Start by obtaining the middle node (mid) of the linked list.
To learn how to get to the middle of a linked list, read the explanation in the Linked List Midpoint problem in the Fast and Slow Pointers chapter.
Then, reverse the second half of the linked list starting at mid. The last node of the original linked list becomes the head of the second half. This second head is used to traverse the newly reversed second half.
To learn how to reverse a linked list in time, read the explanation in the Reverse Linked List problem in this chapter.
The last thing we need to do is check if the first half matches the now-reversed second half. We can do this by simultaneously traversing both halves node by node, and comparing each node from the first half to the corresponding node from the second half. If at any point the node values don't match, it indicates the linked list is not a palindrome.
We can use two pointers (ptr1 and ptr2) to iterate through the first and the reversed second half of the linked list, respectively:
from ds import ListNode
def palindromic_linked_list(head: ListNode) -> bool:
# Find the middle of the linked list and then reverse the second half of the # linked list starting at this midpoint.
mid = find_middle(head)
second_head = reverse_list(mid)
# Compare the first half and the reversed second half of the list
ptr1, ptr2 = head, second_head
res = True
while ptr2:
if ptr1.val != ptr2.val:
res = False
ptr1, ptr2 = ptr1.next, ptr2.next
return res
# From the 'Reverse Linked List' problem.
def reverse_list(head: ListNode) -> ListNode:
prevNode, currNode = None, head
while currNode:
nextNode = currNode.next
currNode.next = prevNode
prevNode = currNode
currNode = nextNode
return prevNode
# From the 'Linked List Midpoint' problem.
def find_middle(head: ListNode) -> ListNode:
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slowbool palindromicLinkedList(ListNode* head) {
// 1. find the middle
ListNode* slow = head;
ListNode* fast = head;
while (fast != nullptr && fast->next != nullptr) {
slow = slow->next;
fast = fast->next->next;
}
// 2. reverse from slow to the end
ListNode* prev = nullptr;
while (slow != nullptr) {
ListNode* nxt = slow->next;
slow->next = prev;
prev = slow;
slow = nxt;
}
// 3. compare the two halves
ListNode* left = head;
ListNode* right = prev;
while (right != nullptr) {
if (left->val != right->val) return false;
left = left->next;
right = right->next;
}
return true;
}public boolean palindromicLinkedList(ListNode head) {
// 1. find the middle
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
// 2. reverse from slow to the end
ListNode prev = null;
while (slow != null) {
ListNode nxt = slow.next;
slow.next = prev;
prev = slow;
slow = nxt;
}
// 3. compare the two halves
ListNode left = head, right = prev;
while (right != null) {
if (left.val != right.val) return false;
left = left.next;
right = right.next;
}
return true;
}Time complexity: The time complexity of palindromic_linked_list is , where denotes the length of the linked list. This is because it involves iterating through the linked list three times: once to find the middle node, once to reverse the second half, and once more to compare the two halves.
Space complexity: The space complexity is .
Two step-by-step walkthroughs of the algorithm below: a valid case (the expected happy path) and an invalid / edge case that exercises the tricky parts. Each step shows the program state as the code runs—the same steps apply to Python, C++ and Java.
slow / left fast / right
| phase | slow / left | fast / right | check |
|---|---|---|---|
| mid: start | 1 | 1 | — |
| mid: step | 2 | 3 | fast.next ok |
| mid: step | 3 | 1 (last) | fast.next null → stop |
| cmp | 1 | 1 | equal |
| cmp | 2 | 2 | equal |
| cmp | 3 | null | right null → return true |
slow / left fast / right mismatch
| phase | slow / left | fast / right | check |
|---|---|---|---|
| mid: start | 1 | 1 | — |
| mid: step | 2 | 3 | fast.next ok |
| mid: step | 3 | 1 (last) | fast.next null → stop |
| cmp | 1 | 1 | equal |
| cmp (stop) | 2 | 4 | 2 ≠ 4 → return false |
Contrast with Dry run 1: there every pair matched and the loop ran to the end → true. Here the first pair (1,1) matches but the second (2,4) differs, so we return false immediately — the middle node is never inspected. Mismatches short-circuit.
Both runs step exactly as the tables above. Python compares left.val != right.val and returns False on the first mismatch; the loop runs while right is not None.
Same steps. C++: if (left->val != right->val) return false; looping while (right != nullptr).
Same steps. Java: if (left.val != right.val) return false; looping while (right != null).
Tip: Confirm if it’s acceptable to modify the linked list. In our solution, we reversed the second half of the linked list which dismantled the input’s initial structure. Why does this matter? Oftentimes, the input data structure should not be modified, particularly if it's shared or accessed concurrently. As such, it’s important to confirm with your interviewer whether input modification is acceptable and to briefly address the implications of this.