Palindrome String – Solution & Complexity
Solution Walkthrough
Step 1: Understanding the Problem
- Return
truewhensreads the same forwards and backwards after two normalizations: drop every non-alphanumeric character, and compare letters without regard to case. - This is not an exact reversal check. For
"A man, a plan, a canal: Panama",s == reverse(s)isfalsebecause the reversed string is"amanaP :lanac a ,nalp a ,nam A"; the normalized form"amanaplanacanalpanama"is a palindrome. - Digits are kept (
"12 3 21"normalizes to"12321", sotrue), and case-folding still has to match letter for letter ("AaB"normalizes to"aab", sofalse).
- Return
Step 2: Filter-and-Reverse vs Two Pointers
approach how it works time extra space Filter + reverse Build cleanedfrom the lower-cased alphanumerics, then compare it with a reversed copyO(n) O(n): cleanedplus its reversed copyTwo pointers Walk inward from both ends of the original s, skipping and comparing in placeO(n) O(1) Filter + reverse is a fine first answer, for example in Python
cleaned = [c.lower() for c in s if c.isalnum()]followed byreturn cleaned == cleaned[::-1]. The two-pointer scan answers the follow-up: it never builds a copy, and it can stop at the first mismatch instead of normalizing the whole string first.Step 3: Use Two Pointers to Compare Characters
- Use a two-pointer technique, one starting from the beginning and one from the end of the string.
- Increment the left pointer and decrement the right pointer after each comparison.
Step 4: Skip Non-Alphanumeric Characters
- As you move the two pointers, skip any characters that are not alphanumeric (like spaces, punctuation).
- The
left < rightguard in each inner loop keeps a pointer from running past the other. - The constraints define alphanumeric as ASCII
A-Z,a-z,0-9. The JavaScript, Go, Rust, Ruby, and C++ (std::isalnumin the default "C" locale) checks are ASCII-only. Python'sstr.isalnum()and Java'sCharacter.isLetterOrDigit()also accept non-ASCII letters and digits such asé, so all versions agree on valid inputs but are not interchangeable for arbitrary Unicode text.
Step 5: Compare Characters (Ignore Case)
- Compare the characters at the left and right pointers, ignoring case.
- If the characters are not equal, the string is not a palindrome and you can return
Falseimmediately. - Otherwise move both pointers inward. Once they meet or cross, every pair has matched, so return
True.
Step 6: Dry run / state trace
Trace
s = "A man, a plan, a canal: Panama"(length 30). Indices are positions in the original string; nothing is copied.# left (index char)right (index char)what happens 1 0 A29 aAvsamatch after lower-casing2 skip 1 ' ', stop at 2m28 mmatch 3–4 3 a, 4n27 a, 26ntwo direct matches 5 skip 5 ,and 6' ', stop at 7a25 amatch 6 skip 8 ' ', stop at 9p24 PpvsPmatches only because case is ignored7 10 lskip 23 ' 'and 22:, stop at 21lmatch 8–9 11 a, 12n20 a, 19ntwo direct matches 10 skip 13 ,and 14' ', stop at 15a18 amatch 11 skip 16 ' ', stop at 17c17 cpointers meet on the middle c; comparing it with itself is harmlessAfter row 11,
left = 18andright = 16, so the loop ends and the function returnsTrue: 11 comparisons, 9 skipped characters, O(1) extra space.Step 7: Failing Example: Early Exit
Trace
s = "race a car"(length 10).left right what happens 0 r9 rmatch 1 a8 amatch 2 c7 cmatch 3 eskip 6 ' ', stop at 5ae≠a: returnFalseimmediatelyIndex 4 is never examined. Filter + reverse would first normalize all 10 characters to
"raceacar"and then find the same mismatch against"racaecar".Step 8: Handle Edge Cases
- Empty string:
rightstarts at-1, soleft < rightis false immediately and the function returnsTrue. - Only punctuation, e.g.
"...!!!": the left skip loop advances untilleft == right(index 5), comparing that!with itself passes, the pointers cross, and the result isTrue. The normalized string is empty, and an empty string reads the same in both directions. - Single character:
left == rightfrom the start, so the loop never runs and the result isTrue.
- Empty string: