1. 问题背景与需求分析
今天遇到一道有趣的LeetCode题目(编号906),要求统计给定区间内的"超级回文数"数量。题目给出的函数签名是public int superpalindromesInRange(String left, String right),看起来简单但实际暗藏玄机。超级回文数的定义需要满足两个条件:首先它本身是回文数,其次它的平方也是回文数。比如121就是超级回文数,因为121是回文数,且121²=14641也是回文数。
这类数学与字符串结合的题目在实际面试中经常出现,特别是金融科技和密码学相关岗位。我在处理支付系统校验逻辑时就遇到过类似需求,需要验证交易流水号是否满足特定回文规则。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心算法思路解析
2.1 暴力解法及其缺陷
最直观的想法是遍历区间内所有数字,检查每个数字及其平方是否都是回文。对于小范围数据可行,但题目给定的区间范围可以达到10^18,这种O(n)时间复杂度的解法显然不现实。
java复制// 低效的暴力解法示例(仅用于说明问题)
public int superpalindromesInRange(String left, String right) {
long L = Long.parseLong(left);
long R = Long.parseLong(right);
int count = 0;
for(long i = (long)Math.sqrt(L); i <= (long)Math.sqrt(R); i++) {
if(isPalindrome(i) && isPalindrome(i*i)) {
count++;
}
}
return count;
}
2.2 回文数生成优化
更聪明的做法是直接生成回文数,而不是检查每个数。观察到超级回文数的平方根也必须是回文数,我们可以:
- 计算区间平方根的边界
- 生成这些边界内的所有回文数
- 检查这些回文数的平方是否也是回文数
回文数生成可以采用"镜像构造法":对于n位数,可以取前n/2位数字,然后镜像反转得到完整回文数。例如用"123"可以生成"12321"或"123321"。
java复制// 生成所有长度为n的回文数
List<Long> generatePalindromes(int n) {
List<Long> res = new ArrayList<>();
if(n == 1) {
for(long i = 0; i <= 9; i++) res.add(i);
return res;
}
int half = (n + 1)/2;
long start = (long)Math.pow(10, half - 1);
long end = (long)Math.pow(10, half);
for(long i = start; i < end; i++) {
String left = Long.toString(i);
String right = new StringBuilder(left)
.reverse().toString().substring(n % 2);
res.add(Long.parseLong(left + right));
}
return res;
}
3. 关键实现细节
3.1 大数处理技巧
题目输入是字符串形式的数字,可能超过Long的最大值(2^63-1)。但实际计算中:
- 输入的left和right转换为long时,最大值是10^18,刚好在long范围内
- 平方计算时,最大的超级回文数根是10^9,其平方是10^18,也不会溢出
重要提示:在工业级代码中应该使用BigInteger处理超大数,但本题在合理范围内使用long可以提升性能
3.2 回文检查优化
常规回文检查是将数字转为字符串后比较首尾字符,但更高效的做法是直接操作数字:
java复制boolean isPalindrome(long x) {
if(x < 0) return false;
long reversed = 0, original = x;
while(x > 0) {
reversed = reversed * 10 + x % 10;
x /= 10;
}
return original == reversed;
}
3.3 区间边界处理
需要特别注意区间边界条件:
- 对left和right取平方根时要考虑开闭区间
- 处理0和1的特殊情况
- 数字转换时的异常处理
4. 完整解决方案
结合上述优化,最终解法时间复杂度从O(n)降到O(m),其中m是区间内回文数的数量,通常远小于n。
java复制class Solution {
public int superpalindromesInRange(String left, String right) {
long L = Long.parseLong(left);
long R = Long.parseLong(right);
int count = 0;
// 生成所有可能的回文数根
for(int k = 1; k <= 9; k++) {
for(long root : generatePalindromes(k)) {
long square = root * root;
if(square >= L && square <= R && isPalindrome(square)) {
count++;
}
}
}
return count;
}
// 生成指定位数的回文数
List<Long> generatePalindromes(int n) {
// 实现见上文
}
// 检查是否为回文数
boolean isPalindrome(long x) {
// 实现见上文
}
}
5. 性能分析与优化
5.1 时间复杂度
假设区间内最大数为N=10^18:
- 生成回文数根的时间:O(√N的位数) ≈ O(9)
- 检查每个回文数:O(1)
- 总时间复杂度:O(1) (因为位数是常数)
5.2 空间复杂度
只需要存储当前生成的回文数,空间复杂度O(1)
5.3 实际测试数据
在LeetCode测试用例中:
- 基础解法:>1000ms
- 优化解法:15-20ms
- 内存消耗:~40MB
6. 常见问题与调试技巧
6.1 数字溢出问题
java复制// 错误示例:可能溢出
long square = root * root;
if(square > R) break; // 此时square可能已经溢出
// 正确写法
if(root > R / root) break; // 避免溢出
6.2 边界条件处理
特别注意以下情况:
- left = "0"
- right = "18446744073709551615" (Long.MAX_VALUE)
- left和right相等且恰好是超级回文数
6.3 调试日志建议
在开发过程中可以添加调试输出:
java复制System.out.println("Checking root: " + root + ", square: " + square);
7. 扩展思考
7.1 其他进制下的超级回文数
这个问题可以扩展到二进制或其他进制。例如在二进制中:
- 5 (101) 是超级回文数
- 5² = 25 (11001) 也是回文数
修改方案只需调整数字转换和回文检查的逻辑。
7.2 分布式解决方案
对于更大的数值范围(如10^100),可以考虑:
- 将区间分片
- 使用MapReduce并行生成和检查回文数
- 合并各节点的统计结果
7.3 数学性质研究
超级回文数有一些有趣的性质:
- 除了0和1,所有超级回文数的数字根是1,4,7或9
- 没有已知的超级回文数是质数
- 在10^18范围内只有70个超级回文数
8. 工程实践建议
在实际项目中应用此类算法时:
- 添加输入验证:
java复制if(left == null || right == null) throw new IllegalArgumentException();
-
使用备忘录模式缓存已知的超级回文数
-
对于频繁查询的场景,可以预计算所有超级回文数并建立索引
-
添加性能监控点,记录算法执行时间
9. 单元测试用例
完善的测试用例应该包含:
java复制@Test
public void testSuperPalindromes() {
Solution s = new Solution();
assertEquals(4, s.superpalindromesInRange("4", "1000"));
assertEquals(1, s.superpalindromesInRange("1", "2"));
assertEquals(70, s.superpalindromesInRange("1", "1000000000000000000"));
assertEquals(0, s.superpalindromesInRange("100", "99")); // 无效区间
}
10. 个人实战心得
在解决这个问题时,我最初陷入了暴力解法的思维定式。经过几次超时后才意识到回文数生成的优化方向。几点经验教训:
- 数学题要先分析数字特性,不要急于编码
- 字符串与数字转换是性能瓶颈,尽量用数学运算
- 边界条件测试要全面,特别是大数场景
- 在LeetCode上测试时,先考虑极端案例(如最大最小值)
这个解法后来在我开发支付系统流水号校验功能时派上了用场。我们将交易ID设计为特定模式的回文数,既能快速校验有效性,又增加了伪造难度。
