Write a function to check whether a string is a palindrome, ignoring case and non-letters.
Clean the string by keeping only letters and digits in lowercase, then compare it with its reverse. This runs in O(n) time. A two-pointer version avoids the extra reversed copy.
Explain it simply
A palindrome reads the same both ways, like "Madam" or "A man, a plan, a canal: Panama". We first normalise the text, then compare.
Line by line
def is_palindrome(s):
cleaned = [c.lower() for c in s if c.isalnum()]
return cleaned == cleaned[::-1]
def is_palindrome_two_pointers(s):
i, j = 0, len(s) - 1
while i < j:
if not s[i].isalnum():
i += 1
elif not s[j].isalnum():
j -= 1
else:
if s[i].lower() != s[j].lower():
return False
i += 1
j -= 1
return True
print(is_palindrome("Madam")) # Output: True
print(is_palindrome("A man, a plan, a canal: Panama")) # Output: True
print(is_palindrome_two_pointers("race a car")) # Output: False
print(is_palindrome("")) # Output: TrueThe two-pointer version moves inward from both ends and skips punctuation, so it never builds a second string.
Complexity
Time O(n) for both. Space O(n) for the cleaned list in the first version, O(1) for the two-pointer version.
What to say in the interview
"I'd normalise with isalnum and lower, then compare to the reverse. If asked to optimise memory, I use two pointers from both ends. An empty string counts as a palindrome."