LeetCode 125. 验证回文串:双指针解法全解析与优化

在字符串处理问题中,回文串判断是经典题型之一,而 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),是更优的空间效率方案,面试中更推荐。

六、总结与面试提示

  1. 验证回文串的核心考点的是「字符预处理」和「双指针遍历」,解题时需注意:
  • 不要遗漏数字字符(容易忽略的细节,导致用例错误);

  • 双指针法的边界条件(left ≤ right,避免遗漏中间字符);

  • 代码优化意识(减少重复操作,平衡时间与空间效率)。

面试中遇到这道题时,优先给出双指针原地解法(体现空间优化思维),再补充预处理思路,能展现更全面的解题能力。同时,可主动提及测试用例设计(如含数字、纯符号、空字符串等),进一步提升面试表现。

相关推荐
客卿1232 小时前
力扣20-有效括号(多家面试题)
算法·leetcode·职场和发展
木井巳2 小时前
【递归算法】快速幂解决 pow(x,n)
java·算法·leetcode·深度优先
帅得不敢出门2 小时前
Android Framework在mk中新增类似PRODUCT_MODEL的变量并传递给buildinfo.sh及prop属性中
android·linux·前端
Maỿbe3 小时前
重走力扣hot的矩阵
算法·leetcode·矩阵
朔北之忘 Clancy3 小时前
2025 年 12 月青少年软编等考 C 语言二级真题解析
c语言·开发语言·c++·学习·算法·青少年编程·题解
小码吃趴菜3 小时前
【无标题】
前端·chrome
2301_790300963 小时前
C++与增强现实开发
开发语言·c++·算法
每天要多喝水3 小时前
贪心算法专题Day18
算法·贪心算法
毅炼4 小时前
hot100打卡——day14
java·数据结构·算法·leetcode·ai·深度优先·哈希算法