1. 概述

在本篇文章中,我们将学习如何判断一个字符串是否是回文串(Palindrome)。回文串是指正着读和反着读都一样的字符串,例如 "radar""madam""12321" 等。

2. 什么是回文串?

回文串是指围绕中心对称的字符串。

举个例子:

如上图所示,第一个字符和最后一个字符相同,第二个字符和倒数第二个字符相同,依此类推。

这可以引出一个递归定义:一个字符串 x = x1x2...xn 是回文串,当且仅当 x1 == xnx2...xn-1 也是回文串。递归终止条件是:

  • 空字符串是回文串(长度为 0)
  • 单个字符也是回文串(长度为 1)

所以,完整的递归定义如下:

2.1 奇数长度与偶数长度的情况

该定义是否适用于奇数和偶数长度的字符串?

  • 偶数长度:递归过程最终会在中间两个字符之间结束,这两个字符是最后一组需要比较的字符。
  • 奇数长度:递归过程最终会到达中间字符,而长度为 1 的字符串本身就是回文串。

示意图如下:

偶数长度的回文串:

奇数长度的回文串:

3. 递归算法实现

我们可以根据上述定义实现一个递归算法:

public boolean isPalindrome(String s) {
    if (s.length() <= 1) {
        return true;
    }
    if (s.charAt(0) != s.charAt(s.length() - 1)) {
        return false;
    }
    return isPalindrome(s.substring(1, s.length() - 1));
}

3.1 时间复杂度分析

最坏情况下(字符串是回文),我们需要检查所有字符对,因此时间复杂度为 **O(n)**。

⚠️ 但要注意,如果字符串非常长,递归调用栈会很深,可能会导致 StackOverflowError。此时建议改用迭代方式实现。

4. 迭代算法实现

为了规避递归带来的栈溢出问题,我们可以将算法改写为迭代版本:

public boolean isPalindrome(String s) {
    int left = 0;
    int right = s.length() - 1;

    while (left <= right) {
        if (s.charAt(left) != s.charAt(right)) {
            return false;
        }
        left++;
        right--;
    }
    return true;
}

4.1 算法说明

  • 初始化两个指针:left 从左向右移动,right 从右向左移动
  • 每次比较 s[left]s[right]
  • 如果不相等,立即返回 false
  • 如果遍历完整个字符串,说明是回文串,返回 true

✅ 优点:无递归调用,避免栈溢出问题
✅ 时间复杂度同样是 O(n)

5. 总结

方法 时间复杂度 是否可能栈溢出 适用场景
递归 O(n) ✅ 是 简单实现、教学用途
迭代 O(n) ❌ 否 实际项目推荐使用

建议:实际开发中推荐使用迭代版本,避免因字符串过长导致的栈溢出问题。
⚠️ 踩坑提醒:注意 Java 中 substring 的索引范围是左闭右开 [start, end),容易出错。

6. 附:完整测试代码

以下是一个完整的测试类,包含多个测试用例:

public class PalindromeCheckerTest {

    @Test
    public void testEmptyString() {
        assertTrue(isPalindrome(""));
    }

    @Test
    public void testSingleCharacter() {
        assertTrue(isPalindrome("a"));
    }

    @Test
    public void testEvenLengthPalindrome() {
        assertTrue(isPalindrome("abba"));
    }

    @Test
    public void testOddLengthPalindrome() {
        assertTrue(isPalindrome("madam"));
    }

    @Test
    public void testNonPalindrome() {
        assertFalse(isPalindrome("hello"));
    }

    @Test
    public void testLongPalindrome() {
        assertTrue(isPalindrome("1234567890987654321"));
    }

    @Test
    public void testLongNonPalindrome() {
        assertFalse(isPalindrome("12345678909876543210"));
    }
}

原始标题:How to Check If a String Is a Palindrome?