Problem overview
The method isPalindrome receives head, the first node of a singly linked list of digits, and returns true when the values read the same from front to back and from back to front.
For [1, 2, 2, 1] the answer is true: the first and last values match (1 and 1), and so do the two in the middle (2 and 2). For [1, 2] the answer is false, because 1 and 2 differ. A list always has at least one node, and a single node is a palindrome.
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. On an array that is easy, since you can reach both ends. A singly linked list only moves forward: each node knows its next node and nothing about the one before it, so there is no way to walk back from the tail.
The simplest fix is to copy the values into an array first, then compare from both ends toward the middle with two indices. The copy costs O(n) extra space. The follow-up asks for O(1) space, which needs a different technique (reversing the second half of the list in place); this lesson takes the array route, which is shorter and harder to get wrong.
Take [1, 2, 3, 2, 1]. The array is [1, 2, 3, 2, 1], left starts at 0 and right at 4. The pair (1, 1) matches, then (2, 2) matches. Now left and right are both 2, so the loop ends: the middle value 3 has no partner and needs no check. The method returns true.
Step by step
- Create an empty array of integers,
vals, withvarbecause it grows.[Int]()is one way to write an empty array ofInt. - Declare
curas a walker, starting athead. Its type isListNode?, the same ashead. - Loop while
cur != nil. When the loop runs,curhas just been checked, socur!is safe. - Append the current value to
vals. - Move
curtocur!.next. After the last node this becomesnil, and the loop ends. - Set
leftto 0, the index of the first value. - Set
righttovals.count - 1, the index of the last value. - Loop while
left < right. When the indices meet or cross, every value has been compared with its mirror. An odd-length list leaves one middle value that is its own mirror. - Return
falseon the first mismatch. One pair of different values is enough to rule out a palindrome. - Move both indices inward: add 1 to
leftand subtract 1 fromright. - Return
trueafter the loop, since every pair matched.
The solution in Swift
class Solution {
func isPalindrome(_ head: ListNode?) -> Bool {
var vals = [Int]()
var cur = head
while cur != nil {
vals.append(cur!.val)
cur = cur!.next
}
var left = 0
var right = vals.count - 1
while left < right {
if vals[left] != vals[right] { return false }
left += 1
right -= 1
}
return true
}
}
Swift details worth knowing:
- Optionals and the force unwrap.
curis aListNode?, so you can't writecur.valdirectly. The!incur!.valis safe here because the loop condition has just proved thatcurisn'tnil. Without that check, unwrappingnilwould crash the program at run time. - Value types and reference types.
valsis an array, a value type: it lives in a local variable and holds copies of the integers.curonly copies a reference to a node, so moving it never changes the list. varversuslet.vals,cur,leftandrightall change, so each is declared withvar. Swift reports a warning for avarthat never changes, which helps catch mistakes.- Integer arithmetic.
vals.count - 1is a plainInt. AnIntin Swift can go negative, and the lists here are never empty, sorightstarts at 0 or more. Even for an empty array,rightwould be -1 and the loop would simply not run.
Complexity
Time: O(n). Copying the values visits each node once. The comparison loop makes at most n / 2 steps.
Space: O(n). The array holds one integer per node. This is the price of the simple approach; the in-place alternative gets this down to O(1) at the cost of changing the list while it works.
Edge cases and common mistakes
- A single node.
[1]givesleft = 0andright = 0, so the loop never runs and the method returnstrue. - Odd length.
[1, 2, 1]is a palindrome. The conditionleft < rightstops at the middle element. Usingleft <= rightwould still give the right answer, because a value always equals itself, but it does a pointless comparison. - Matching ends, different middle.
[1, 2, 3, 1, 2]is not a palindrome. Stopping after the first matching pair would be wrong; every pair has to match. - Comparing counts instead of positions.
[1, 2, 2, 1]and[1, 2, 1, 2]contain the same values, but only the first is a palindrome. The comparison has to pair positioniwith positionn - 1 - i. - Walking back through the list. There is no
prevpointer, so any attempt to go from the tail to the head needs the array, a stack, or reversing part of the list.
Testing
[1, 2, 2, 1]givestrue, and[1, 2]givesfalse.[1]givestrue.[1, 2, 1]and[1, 2, 3, 2, 1]givetrue.[1, 2, 3, 1, 2],[1, 2, 1, 2]and[1, 0, 0]givefalse.
