Problem overview
The method isPalindrome receives the head of a singly linked list of digits and returns true if the values read the same from front to back and from back to front.
For [1, 2, 2, 1] the first value matches the last and the second matches the third, so the answer is true. For [1, 2] the first value is 1 and the last is 2, so the answer is false. The list has at least one node, and every value is between 0 and 9.
Approach: make the list indexable
Checking a palindrome means comparing the first element with the last, the second with the second to last, and so on. An array makes that easy, because any position can be read directly. A singly linked list can't do it: each node knows only the one after it, so there is no way to step backwards from the tail.
So the lesson copies the values into an array first, then compares from both ends with two indices that move toward the middle. When the indices meet or cross, every value has been compared with its mirror.
Building the array costs one pass over the list and the comparison costs another half pass, so the work is linear. What the array costs is memory: one slot for every node.
The statement's follow-up asks for O(1) extra space, which needs a different technique: reverse the second half of the list in place and compare the halves. This lesson's array approach is simpler and uses O(n) space.
Step by step
- Create
valsas aconsttypednumber[], starting empty. It isconstbecause the variable never points at a different array, even though the array's contents grow. - Create
cur, typedListNode | null, starting athead. It walks the list. - Loop while
cur !== null. The list ends at anullpointer, so that is the stopping condition. - Push
cur.valontovalsinside the loop. - Advance
curtocur.next. Forgetting this line makes the loop run forever. - Set
leftto 0, the first index. - Set
righttovals.length - 1, the last index. - Loop while
left < right. Once the indices meet, the remaining value, if any, is the middle of an odd-length list. It has no partner, so it needs no check. - Return
falseon the first mismatch. One unequal pair is enough to rule out a palindrome. - Move the indices inward: add 1 to
leftand subtract 1 fromright. - Return
trueafter the loop. Every pair matched.
The solution in TypeScript
class Solution {
isPalindrome(head: ListNode | null): boolean {
const vals: number[] = [];
let cur: ListNode | null = head;
while (cur !== null) {
vals.push(cur.val);
cur = cur.next;
}
let left: number = 0;
let right: number = vals.length - 1;
while (left < right) {
if (vals[left] !== vals[right]) return false;
left += 1;
right -= 1;
}
return true;
}
}
Some TypeScript points about this code:
constprotects the binding, not the contents.vals.push(...)is allowed on aconstarray. Only an assignment such asvals = []would be an error.- The annotations are optional.
left: number = 0means the same aslet left = 0, since TypeScript infersnumber. They are written out here for practice, and they disappear when the code is compiled to JavaScript. - Numbers compare with
!==by value. Unlike nodes, which compare by reference, twonumbervalues compare equal whenever they hold the same number. That is whyvals[left] !== vals[right]is the right test for values. Comparing the nodes themselves would never match, since every node is a distinct object. - No negative index problem. The list has at least one node, so
vals.length - 1is at least 0. If the list could be empty,rightwould be -1 and the loop would simply not run, returningtrue.
Complexity
Time: O(n). The first loop visits each node once, and the second loop makes at most n / 2 comparisons.
Space: O(n). The array holds one number for every node. This misses the follow-up's O(1) space target, but it has the same linear time.
Edge cases and common mistakes
- A single node.
[7]givesleft = 0andright = 0, the loop doesn't run, and the answer istrue. - Odd length.
[1, 2, 3, 2, 1]compares the 1s, then the 2s, then stops with both indices on 3. The answer istrue. - Mismatch deep inside.
[1, 2, 3, 4, 1]matches the 1s, then compares 2 with 4 and returnsfalse. - Using
left <= right. It also works, because the middle value would be compared with itself, but it does one useless comparison. - Letting the indices cross. With a condition such as
left < vals.length,righteventually goes below 0, andvals[-1]isundefined, which never equals a digit. A palindrome would then be reported asfalse. - Comparing nodes instead of
val. Pushingcurinstead ofcur.valwould make the equality checks compare objects, and almost every list would be called a non-palindrome. - Reversing in place and forgetting to restore. If you solve the follow-up and reverse half of the list, the caller's list is changed unless you reverse it back.
Testing
[1, 2, 2, 1]givestrue.[1, 2]givesfalse.[7]givestrue.[1, 2, 3, 2, 1]givestrue.[1, 2, 3, 4, 1]givesfalse.
