在字符串处理问题中,回文串判断是经典题型之一,而 LeetCode 125 题「验证回文串」更是面试中的高频考点。该题目要求我们在忽略非字母数字字符、统一大小写后,判断字符串是否正着读与反着读一致。本文将结合给出的双指针解法,从题目理解、代码分析、常见问题到优化方向,逐一展开讲解,帮助大家彻底掌握这道题的解题思路。
一、题目核心要求拆解
首先明确题目中的关键约束条件,避免解题时出现偏差:
-
仅考虑字母和数字字符,忽略空格、标点符号等非字母数字字符;
-
不区分大小写,例如 'A' 和 'a' 视为相同字符;
-
空字符串或仅含非字母数字字符的字符串,视为回文串(因为处理后为空,满足回文条件)。
示例:输入 "A man, a plan, a canal: Panama",处理后为 "amanaplanacanalpanama",是回文串,返回 true;输入 "race a car",处理后为 "raceacar",非回文串,返回 false。
二、给出代码的逻辑解析
该解法采用「双指针法」,是回文串判断的最优思路之一,时间复杂度 O(n)(n 为字符串长度,每个字符最多被访问一次),空间复杂度 O(1)(仅用常数额外空间)。核心分为两个函数:
1. 辅助函数 isReasonable:字符有效性处理
该函数的作用是判断字符是否为字母/数字,并对字母进行大小写统一,返回处理后的 ASCII 码(无效字符返回 -1),逻辑如下:
-
若为小写字母(ASCII 97-122):直接返回原 ASCII 码;
-
若为大写字母(ASCII 65-90):转换为小写(加 32)后返回;
-
若为数字(ASCII 48-57):直接返回原 ASCII 码;
-
其他字符(空格、标点等):返回 -1,表示无效。
这里补充一个细节:数字字符无需转换大小写,直接保留原码即可,这也是该函数相比初始版本的修正点(初始版本未处理数字,导致 "0P" 等用例判断错误)。
2. 主函数 isPalindrome:双指针核心判断
采用左右双指针相向遍历,逐步过滤无效字符并对比有效字符,逻辑如下:
-
初始化左指针 left = 0(字符串起始),右指针 right = s.length - 1(字符串末尾);
-
循环条件:left ≤ right(确保所有字符都被对比);
-
左指针遇到无效字符(isReasonable 返回 -1):left 右移,跳过该字符;
-
右指针遇到无效字符(isReasonable 返回 -1):right 左移,跳过该字符;
-
左右指针均指向有效字符,但处理后的值不相等:直接返回 false(非回文串);
-
左右指针字符相等:双指针同时向中间移动,继续对比。
循环结束后,若所有有效字符都对称匹配,返回 true(是回文串)。
三、代码测试与常见问题排查
1. 测试用例验证
我们用几个典型用例测试代码正确性:
-
用例 1:"A man, a plan, a canal: Panama" → 处理后为纯字母串,对称匹配,返回 true;
-
用例 2:"race a car" → 处理后为 "raceacar",中间字符不匹配,返回 false;
-
用例 3:"0P" → 左指针 '0'(有效,ASCII 48),右指针 'P'(转小写后 ASCII 112),值不相等,返回 false(修正后正确);
-
用例 4:" " → 无有效字符,返回 true;
-
用例 5:"a0a" → 数字 0 有效,处理后为 "a0a",对称匹配,返回 true。
2. 代码中可优化的细节
虽然该代码能正确解题,但存在一处冗余操作,可进一步优化:
问题:在循环中多次调用 isReasonable(leftCode) 和 isReasonable(rightCode),同一字符的有效性处理被重复执行,增加了不必要的开销。
优化方案:将有效字符处理结果缓存到变量中,避免重复调用函数。
四、优化后的最终代码
typescript
function isPalindrome(s: string): boolean {
let left = 0, right = s.length - 1;
while (left <= right) {
const leftCode = s.charCodeAt(left);
const rightCode = s.charCodeAt(right);
// 缓存有效字符处理结果,避免重复调用
const leftValid = isReasonable(leftCode);
const rightValid = isReasonable(rightCode);
if (leftValid === -1) {
left++;
} else if (rightValid === -1) {
right--;
} else if (leftValid !== rightValid) {
return false;
} else {
left++;
right--;
}
}
return true;
};
// 字符有效性处理:字母转小写,数字保留,其他无效
function isReasonable(code: number): number {
if (code >= 97 && code <= 122) { // 小写字母
return code;
} else if (code >= 65 && code <= 90) { // 大写字母转小写
return code + 32;
} else if (code >= 48 && code <= 57) { // 数字
return code;
} else { // 非字母数字
return -1;
}
}
优化后,每个字符的有效性处理仅执行一次,代码效率更高,同时可读性不受影响。
五、其他解题思路对比
除了双指针法,还有一种直观思路:先预处理字符串(过滤非字母数字、转小写),再判断处理后的字符串是否为回文。
typescript
function isPalindrome(s: string): boolean {
// 预处理:过滤非字母数字,转小写
const processed = s.replace(/[^a-zA-Z0-9]/g, '').toLowerCase();
// 判断回文(双指针或反转对比)
let left = 0, right = processed.length - 1;
while (left < right) {
if (processed[left] !== processed[right]) {
return false;
}
left++;
right--;
}
return true;
}
该思路代码更简洁,但预处理阶段会额外占用 O(n) 空间(存储处理后的字符串),适合对代码简洁度要求高、空间限制宽松的场景;而双指针法(原地处理)空间复杂度 O(1),是更优的空间效率方案,面试中更推荐。
六、总结与面试提示
- 验证回文串的核心考点的是「字符预处理」和「双指针遍历」,解题时需注意:
-
不要遗漏数字字符(容易忽略的细节,导致用例错误);
-
双指针法的边界条件(left ≤ right,避免遗漏中间字符);
-
代码优化意识(减少重复操作,平衡时间与空间效率)。
面试中遇到这道题时,优先给出双指针原地解法(体现空间优化思维),再补充预处理思路,能展现更全面的解题能力。同时,可主动提及测试用例设计(如含数字、纯符号、空字符串等),进一步提升面试表现。