力扣解题-125. 验证回文串

4 阅读6分钟

力扣解题-125. 验证回文串

如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后,短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。

字母和数字都属于字母数字字符。

给你一个字符串 s,如果它是 回文串 ,返回 true ;否则,返回 false 。

示例 1:

输入: s = "A man, a plan, a canal: Panama"

输出:true

解释:"amanaplanacanalpanama" 是回文串。

示例 2:

输入:s = "race a car"

输出:false

解释:"raceacar" 不是回文串。

示例 3:

输入:s = " "

输出:true

解释:在移除非字母数字字符之后,s 是一个空字符串 "" 。

由于空字符串正着反着读都一样,所以是回文串。

提示:

1 <= s.length <= 2 * 105

s 仅由可打印的 ASCII 字符组成

Related Topics

双指针、字符串


第一次解答

解题思路

核心方法:预处理 + 双指针验证,先通过正则替换移除非字母数字字符并转小写,再用左右双指针从两端向中间逐字符比对,逻辑直观但正则替换存在性能损耗。

核心逻辑拆解

验证回文串的核心是“先清洗字符串,再验证对称”:

  1. 清洗字符串:去掉所有非字母数字的字符(如逗号、空格、冒号),并将所有字母转为小写,得到纯字母数字的小写字符串;
  2. 边界处理:清洗后为空或只有1个字符,直接返回true(天然是回文);
  3. 双指针验证:左指针从开头、右指针从结尾同时向中间移动,逐字符比对是否相等;
  4. 只要有一对字符不相等,返回false;全部比对完成则返回true。
性能损耗分析
  • 时间复杂度:O(n)(正则替换O(n) + 双指针遍历O(n)),但正则替换的常数因子大;
  • 空间复杂度:O(n)(生成清洗后的新字符串);
  • 核心损耗点:
    1. replaceAll("[^a-zA-Z0-9]", "")使用正则表达式匹配替换,正则引擎的匹配过程耗时且生成新字符串;
    2. 清洗后的字符串占用额外内存,导致内存开销高;
  • 性能表现:耗时14ms仅击败10.50%用户,内存45.7MB击败10.71%用户,正是正则替换的损耗导致。
    public boolean isPalindrome(String s) {
        String t = s.replaceAll("[^a-zA-Z0-9]", "").toLowerCase();
        if(t.length()==0 || t.length()==1){
            return true;
        }
        int left =0;
        int right = t.length()-1;
        while (left<=right){
            if (t.charAt(left)!=t.charAt(right)){
                return false;
            }
            left++;
            right--;
        }
        return true;
    }

第二次解答

解题思路

核心方法:预处理 + 字符串反转比对,清洗字符串的逻辑与第一次一致,改为通过StringBuilder.reverse()反转字符串后直接比对,逻辑更简洁但性能仍受限于正则替换。

核心逻辑拆解

该解法是第一次解答的简化版:

  1. 同样先清洗字符串(去非字母数字+转小写);
  2. 将清洗后的字符串反转,若反转前后完全一致,则是回文串;
  3. 无需手动双指针遍历,利用String.equals()直接比对。
性能说明
  • 时间复杂度:O(n)(正则替换O(n) + 反转字符串O(n) + 比对O(n));
  • 空间复杂度:O(n)(清洗后的字符串 + 反转后的字符串);
  • 核心损耗:相比第一次解答,多了反转字符串的O(n)操作,且StringBuilder.toString()生成新字符串,内存开销进一步增加;
  • 性能表现:耗时14ms(与第一次持平,正则仍为主要耗时),内存46.9MB仅击败5.03%用户,内存损耗更严重。
    public boolean isPalindrome(String s) {
        String t = s.replaceAll("[^a-zA-Z0-9]", "").toLowerCase();
        return t.equals(new StringBuilder(t).reverse().toString());
    }

示例解答

解题思路

解法1:原地双指针法(最优解)

核心方法:原字符串直接双指针遍历 + 字符过滤转换,无需生成清洗后的新字符串,在遍历过程中跳过非字母数字字符、实时转换大小写,空间复杂度O(1),是本题的最优解。

核心逻辑拆解

原地双指针的核心是“边过滤边比对”,无需预处理整个字符串:

  1. 左指针left从0开始,右指针right从字符串末尾开始;
  2. 移动左指针,跳过所有非字母数字字符,找到第一个有效字符;
  3. 移动右指针,跳过所有非字母数字字符,找到最后一个有效字符;
  4. 将两个有效字符转为小写后比对,若不相等直接返回false;
  5. 若相等,左指针右移、右指针左移,重复上述步骤,直到左右指针相遇。
关键优化点
  1. 字符有效性判断:用Character.isLetterOrDigit(char c)替代正则,直接判断字符是否为字母/数字,无正则开销;
  2. 大小写转换:用Character.toLowerCase(char c)实时转换,无需提前转换整个字符串;
  3. 原地遍历:无新字符串生成,仅使用两个指针变量,空间复杂度O(1)。
代码实现
public boolean isPalindrome(String s) {
    if (s == null || s.length() == 0) {
        return true;
    }
    int left = 0;
    int right = s.length() - 1;
    while (left < right) {
        // 左指针跳过非字母数字字符
        while (left < right && !Character.isLetterOrDigit(s.charAt(left))) {
            left++;
        }
        // 右指针跳过非字母数字字符
        while (left < right && !Character.isLetterOrDigit(s.charAt(right))) {
            right--;
        }
        // 转换为小写后比对
        char leftChar = Character.toLowerCase(s.charAt(left));
        char rightChar = Character.toLowerCase(s.charAt(right));
        if (leftChar != rightChar) {
            return false;
        }
        // 指针移动
        left++;
        right--;
    }
    return true;
}
性能优势
  • 时间复杂度:O(n)(仅一次遍历,每个字符最多被访问一次);
  • 空间复杂度:O(1)(无新字符串生成,仅使用指针和临时字符变量);
  • 性能表现:耗时可降至1~2ms(击败99%+用户),内存占用≤40MB(击败90%+用户),是本题的最优写法。
解法2:手动字符过滤法(替代正则的预处理方案)

核心方法:手动遍历过滤字符 + 双指针,用手动遍历替代正则替换清洗字符串,减少正则引擎的开销,性能优于前两种解法。

代码实现
public boolean isPalindrome(String s) {
    // 手动过滤字符,避免正则开销
    StringBuilder sb = new StringBuilder();
    for (char c : s.toCharArray()) {
        if (Character.isLetterOrDigit(c)) {
            sb.append(Character.toLowerCase(c));
        }
    }
    String t = sb.toString();
    int left = 0, right = t.length() - 1;
    while (left < right) {
        if (t.charAt(left) != t.charAt(right)) {
            return false;
        }
        left++;
        right--;
    }
    return true;
}
性能说明
  • 时间复杂度:O(n)(手动过滤O(n) + 双指针O(n)),手动过滤的常数因子远小于正则;
  • 空间复杂度:O(n)(仍生成清洗后的字符串);
  • 优势:性能介于正则解法和原地双指针解法之间,逻辑比原地双指针简单,比正则解法高效。

总结

  1. 正则预处理解法(第一次/第二次):逻辑直观但性能差,正则替换是核心损耗,仅适合快速编写代码;
  2. 手动过滤+双指针:性能优于正则解法,逻辑简洁,是工程中折中选择;
  3. 原地双指针法(最优解):空间O(1)、时间O(n)且常数因子小,无额外内存开销,是本题的最优写法;
  4. 关键优化技巧:
    • 避免使用正则处理简单字符过滤,优先用Character.isLetterOrDigit()/toLowerCase()
    • 尽可能在原字符串上操作,减少新字符串生成;
    • 双指针遍历过程中提前终止(发现不匹配直接返回),减少无效操作。