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;
}
核心逻辑:
- 保存原始数字副本(后续比较用)
- 通过取模运算逐位提取数字
- 从低位到高位构建反转数
- 比较原数和反转数
测试用例:
@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
- 取末位1 →
- 比较:
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);
}
核心逻辑:
- 数字转字符串
- 使用StringBuilder反转字符串
- 比较原字符串和反转字符串
测试用例:
@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);
}
核心逻辑:
- 递归终止条件:原数处理完毕(number=0)
- 每次递归处理一位数字:
- 取末位数字
- 构建反转数
- 剩余数字继续递归
- 最终比较反转数与原数
测试用例:
@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)
- 处理1 →
- 终止:返回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;
}
核心逻辑:
- 处理边界情况(负数/末尾0)
- 反转后半部分数字:
- 当原数大于反转数时继续处理
- 每次取末位添加到反转数
- 比较两种情况:
- 数字长度为偶数:直接比较
- 数字长度为奇数:反转数/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
- 取1 →
- 终止(12 < 123)
- 比较:
12 == 123/10
→12 == 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;
}
核心逻辑:
- 计算最高位除数(如12321的divisor=10000)
- 循环比较首末位:
- 取首位:
number / divisor
- 取末位:
number % 10
- 不匹配则返回false
- 取首位:
- 去掉首末位后继续比较:
- 去掉首位:
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%计算量)
- ✅ 极致性能:逐位比较法(完全避免反转)
- ❌ 生产环境:避免递归法(栈溢出风险)
- ❌ 内存敏感:避免字符串转换法(额外对象开销)
理解这些实现方式的差异,能帮助我们在实际开发中根据具体需求(性能、内存、可读性)做出最佳选择。