1. 概述

回文数是指正读反读都相同的数字。本文将探讨在Java中判断回文数的多种实现方式,包括迭代法、递归法以及几种优化方案,帮助开发者根据实际场景选择最适合的方案。

2. 问题定义

给定一个正整数 N,判断它是否是回文数。

输入: N = 12321
输出:
解释: 12321 反转后仍是 12321,与原数相同。

输入: N = 123
输出:
解释: 123 反转后为 321,与原数不同。

下面我们开始探索不同的实现方案。

3. 迭代法实现

最直观的方式是反转数字后比较原数和反转数:

public static boolean isPalindromeIterative(int number) {
    int originalNumber = number;
    int reversedNumber = 0;

    while (number > 0) {
        int digit = number % 10;
        reversedNumber = reversedNumber * 10 + digit;
        number /= 10;
    }
    return originalNumber == reversedNumber;
}

核心逻辑:

  1. 保存原始数字副本(后续比较用)
  2. 通过取模运算逐位提取数字
  3. 从低位到高位构建反转数
  4. 比较原数和反转数

测试用例:

@Test
void givenNumber_whenUsingIterativeApproach_thenCheckPalindrome() {     
    assertTrue(PalindromeNumber.isPalindromeIterative(12321));
    assertFalse(PalindromeNumber.isPalindromeIterative(123));
}

执行流程(以12321为例):

  • 初始化:originalNumber=12321, reversedNumber=0
  • 循环处理:
    • 取末位1 → reversedNumber=1, number=1232
    • 取末位2 → reversedNumber=12, number=123
    • 取末位3 → reversedNumber=123, number=12
    • 取末位2 → reversedNumber=1232, number=1
    • 取末位1 → reversedNumber=12321, number=0
  • 比较:12321 == 12321 → 返回true

复杂度分析:

  • 时间复杂度:O(n)(n为数字位数)
  • 空间复杂度:O(1)

4. 字符串转换法

将数字转为字符串后利用内置方法反转:

public static boolean isPalindromeString(int number) {
    String original = String.valueOf(number);
    String reversed = new StringBuilder(original).reverse().toString();
    return original.equals(reversed);
}

核心逻辑:

  1. 数字转字符串
  2. 使用StringBuilder反转字符串
  3. 比较原字符串和反转字符串

测试用例:

@Test
void givenNumber_whenUsingStringApproach_thenCheckPalindrome() {
    assertTrue(PalindromeNumber.isPalindromeString(12321));
    assertFalse(PalindromeNumber.isPalindromeString(123));
}

执行流程(以12321为例):

  • 转字符串:original="12321"
  • 反转字符串:reversed="12321"
  • 比较:"12321".equals("12321") → 返回true

复杂度分析:

  • 时间复杂度:O(n)(字符串反转需要遍历)
  • 空间复杂度:O(n)(需要存储字符串)

⚠️ 注意:此方法虽然代码简洁,但会创建额外字符串对象,内存开销较大。

5. 递归法实现

通过递归函数反转数字:

public static boolean isPalindromeRecursive(int number) {
    return isPalindromeHelper(number, 0) == number;
}

private static int isPalindromeHelper(int number, int reversedNumber) {
    if (number == 0) {
        return reversedNumber;
    }
    reversedNumber = reversedNumber * 10 + number % 10;
    return isPalindromeHelper(number / 10, reversedNumber);
}

核心逻辑:

  1. 递归终止条件:原数处理完毕(number=0)
  2. 每次递归处理一位数字:
    • 取末位数字
    • 构建反转数
    • 剩余数字继续递归
  3. 最终比较反转数与原数

测试用例:

@Test
void givenNumber_whenUsingRecursiveApproach_thenCheckPalindrome() {
    assertTrue(PalindromeNumber.isPalindromeRecursive(12321));
    assertFalse(PalindromeNumber.isPalindromeRecursive(123));
}

执行流程(以12321为例):

  • 初始调用:isPalindromeHelper(12321, 0)
  • 递归过程:
    • 处理1 → reversed=1 → 调用isPalindromeHelper(1232, 1)
    • 处理2 → reversed=12 → 调用isPalindromeHelper(123, 12)
    • 处理3 → reversed=123 → 调用isPalindromeHelper(12, 123)
    • 处理2 → reversed=1232 → 调用isPalindromeHelper(1, 1232)
    • 处理1 → reversed=12321 → 调用isPalindromeHelper(0, 12321)
  • 终止:返回12321
  • 比较:12321 == 12321 → 返回true

复杂度分析:

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)(递归调用栈深度)

缺点:递归深度较大时可能导致栈溢出,实际开发中慎用。

6. 半反转法

只反转数字的后半部分与前半部分比较:

public static boolean isPalindromeHalfReversal(int number) {
    if (number < 0 || (number % 10 == 0 && number != 0)) {
        return false;
    }
    int reversedNumber = 0;
    while (number > reversedNumber) {
        reversedNumber = reversedNumber * 10 + number % 10;
        number /= 10;
    }
    return number == reversedNumber || number == reversedNumber / 10;
}

核心逻辑:

  1. 处理边界情况(负数/末尾0)
  2. 反转后半部分数字:
    • 当原数大于反转数时继续处理
    • 每次取末位添加到反转数
  3. 比较两种情况:
    • 数字长度为偶数:直接比较
    • 数字长度为奇数:反转数/10后比较

测试用例:

@Test
void givenNumber_whenUsingHalfReversalApproach_thenCheckPalindrome() {
    assertTrue(PalindromeNumber.isPalindromeHalfReversal(12321));
    assertFalse(PalindromeNumber.isPalindromeHalfReversal(123));
}

执行流程(以12321为例):

  • 初始:number=12321, reversed=0
  • 循环:
    • 取1 → reversed=1, number=1232
    • 取2 → reversed=12, number=123
    • 取3 → reversed=123, number=12
  • 终止(12 < 123)
  • 比较:12 == 123/1012 == 12 → 返回true

复杂度分析:

  • 时间复杂度:O(n/2) → O(n)
  • 空间复杂度:O(1)

优势:比全反转法效率更高,尤其适合大数处理。

7. 逐位比较法

直接比较首位和末位数字,无需反转:

public static boolean isPalindromeDigitByDigit(int number) {
    if (number < 0) {
        return false;
    }
    int divisor = 1;
    while (number / divisor >= 10) {
        divisor *= 10;
    }
    while (number != 0) {
        int leading = number / divisor;
        int trailing = number % 10;
        if (leading != trailing) {
            return false;
        }
        number = (number % divisor) / 10;
        divisor /= 100;
    }
    return true;
}

核心逻辑:

  1. 计算最高位除数(如12321的divisor=10000)
  2. 循环比较首末位:
    • 取首位:number / divisor
    • 取末位:number % 10
    • 不匹配则返回false
  3. 去掉首末位后继续比较:
    • 去掉首位:number % divisor
    • 去掉末位:/10
    • 调整除数:divisor /= 100

测试用例:

@Test
void givenNumber_whenUsingDigitByDigitApproach_thenCheckPalindrome() {
    assertTrue(PalindromeNumber.isPalindromeDigitByDigit(12321));
    assertFalse(PalindromeNumber.isPalindromeDigitByDigit(123));
}

执行流程(以12321为例):

  • 初始化:divisor=10000
  • 第一轮:
    • 首位1,末位1 → 匹配
    • 更新:number=232, divisor=100
  • 第二轮:
    • 首位2,末位2 → 匹配
    • 更新:number=3, divisor=1
  • 第三轮:
    • 首位3,末位3 → 匹配
    • 更新:number=0 → 结束
  • 返回true

复杂度分析:

  • 时间复杂度:O(n/2) → O(n)
  • 空间复杂度:O(1)

优势:完全避免数字反转,计算效率高。

8. 总结

本文探讨了Java中判断回文数的五种实现方案,各方案特点对比:

实现方案 时间复杂度 空间复杂度 适用场景
迭代法 O(n) O(1) 通用方案,代码直观
字符串转换法 O(n) O(n) 简单场景,牺牲内存换可读性
递归法 O(n) O(n) 学术演示,生产环境慎用
半反转法 O(n) O(1) 大数处理,性能优化首选
逐位比较法 O(n) O(1) 极致性能需求,避免反转操作

选择建议

  • ✅ 日常开发:迭代法(平衡可读性和性能)
  • ✅ 大数处理:半反转法(减少50%计算量)
  • ✅ 极致性能:逐位比较法(完全避免反转)
  • ❌ 生产环境:避免递归法(栈溢出风险)
  • ❌ 内存敏感:避免字符串转换法(额外对象开销)

理解这些实现方式的差异,能帮助我们在实际开发中根据具体需求(性能、内存、可读性)做出最佳选择。