Valid Palindrome
Problem Statement
A phrase is a palindrome if, after converting all uppercase letters into lowercase letters and removing all non-alphanumeric characters, it reads the same forward and backward. Alphanumeric characters include letters and numbers.
Given a string s, return true if it is a palindrome, or false otherwise.
Example 1:
Input: s = āA man, a plan, a canal: Panamaā
Output: true
Explanation: āamanaplanacanalpanamaā is a palindrome.
Example 2:
Input: s = ārace a carā
Output: false
Explanation: āraceacarā is not a palindrome.
Approach: Two Pointers
A brute force approach would be to create a new, filtered string (by stripping out punctuation and spaces and converting to lowercase), and then comparing it to its reverse. However, creating new strings and reversing them takes extra memory and unnecessary linear passes.
We can optimize this to space using the Two Pointers technique.
We place one pointer at the beginning of the string (left) and one at the end (right). We move them towards the center, skipping any characters that arenāt letters or numbers. When both pointers are resting on valid alphanumeric characters, we compare them (ignoring case). If they match, we continue moving inward. If they donāt, we immediately return false.
- Initialize
left = 0andright = s.length - 1. - Start a
whileloop that runs as long asleft < right. - If the character at
leftis not alphanumeric, incrementleftto skip it. - Otherwise, if the character at
rightis not alphanumeric, decrementrightto skip it. - If both characters are alphanumeric, compare their lowercase versions:
- If they are not equal, return
false. - If they are equal, increment
leftand decrementrightto check the next pair.
- If they are not equal, return
- If the loop completes and the pointers cross without any mismatches, return
true.
Solution
/**
* @param {string} s
* @return {boolean}
*/
function isPalindrome(s) {
let left = 0;
let right = s.length - 1;
// Helper function to check if a character is alphanumeric using Regex
const isAlphanumeric = (char) => /^[a-z0-9]+$/i.test(char);
while (left < right) {
if (!isAlphanumeric(s[left])) {
left++;
} else if (!isAlphanumeric(s[right])) {
right--;
} else {
if (s[left].toLowerCase() !== s[right].toLowerCase()) {
return false;
}
left++;
right--;
}
}
return true;
};
Note: We use a Regular Expression (/^[a-z0-9]+$/i) here because it is highly readable and standard in production codebases. If absolute maximum performance is required in a strict algorithm setting, comparing raw character codes (charCodeAt) is technically faster.
Complexity Analysis
- Time Complexity: where is the length of the string. Both pointers only traverse the string once, moving towards the center, meaning we look at each character at most once.
- Space Complexity: auxiliary space. We are only maintaining two integer pointers (
leftandright), so the memory footprint is strictly constant regardless of the string size.