1. 问题背景与核心概念解析
今天要讨论的是LeetCode第906题"超级回文数"的解法。这道题在算法面试中属于中等偏难的题目,主要考察对回文数特性的理解以及如何高效处理大数范围的遍历问题。我们先明确几个关键概念:
超级回文数是指一个数本身是回文数,并且它的平方也是一个回文数。例如121就是一个超级回文数,因为121的平方14641也是一个回文数。
题目给出的函数签名是:
java复制public int superpalindromesInRange(String left, String right)
这意味着我们需要在给定的范围[left, right]内找出所有满足条件的超级回文数,并返回它们的数量。由于left和right是以字符串形式给出的,暗示了数值可能非常大(超过普通整数类型的表示范围)。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 解题思路分析与优化策略
2.1 暴力法的局限性
最直观的想法是遍历给定范围内的所有整数,检查每个数及其平方是否都是回文数。但这种方法的效率极低,特别是当范围很大时(比如1e18),计算量会变得不可接受。
2.2 回文数生成法
更聪明的做法是直接生成回文数,然后检查其平方是否也是回文数。因为回文数比普通整数少得多,这样可以大大减少需要检查的数字数量。
回文数可以分为两类:
- 奇数长度:如121
- 偶数长度:如1221
我们可以分别生成这两种类型的回文数,然后检查它们的平方是否也是回文数。
2.3 数学边界条件
由于题目中的范围可能非常大(比如1e18),我们需要特别注意数值溢出的问题。在Java中,可以使用long类型来处理大数,但即便如此,当数字接近Long.MAX_VALUE时仍需小心。
3. 详细实现步骤
3.1 回文数生成器
我们需要实现一个能够生成所有可能回文数的函数。这里可以采用"镜像"方法:
java复制private long createPalindrome(long prefix, boolean isOdd) {
long res = prefix;
if (isOdd) {
prefix /= 10;
}
while (prefix > 0) {
res = res * 10 + prefix % 10;
prefix /= 10;
}
return res;
}
这个函数接受一个前缀数字和一个布尔值(指示生成奇数还是偶数长度的回文数),然后通过镜像前缀来构造完整的回文数。
3.2 回文数检查
我们需要一个辅助函数来检查一个数是否是回文数:
java复制private boolean isPalindrome(long x) {
if (x < 0) return false;
long reversed = 0;
long original = x;
while (x > 0) {
reversed = reversed * 10 + x % 10;
x /= 10;
}
return original == reversed;
}
3.3 主函数实现
现在我们可以实现主函数了:
java复制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 < 100000; k++) {
// 生成奇数长度的回文数
long pal = createPalindrome(k, true);
long square = pal * pal;
if (square > R) break;
if (square >= L && isPalindrome(square)) {
count++;
}
}
for (int k = 1; k < 100000; k++) {
// 生成偶数长度的回文数
long pal = createPalindrome(k, false);
long square = pal * pal;
if (square > R) break;
if (square >= L && isPalindrome(square)) {
count++;
}
}
return count;
}
4. 算法复杂度分析
4.1 时间复杂度
我们生成回文数的上限是100000,因此外层循环最多执行200000次(奇数和偶数各100000次)。每次循环内部的操作都是常数时间,因此总时间复杂度是O(1)。
4.2 空间复杂度
我们只使用了常数数量的额外空间,因此空间复杂度是O(1)。
5. 边界条件与特殊测试用例
5.1 边界条件处理
- 当left和right相等时,只需要检查这一个数是否是超级回文数
- 当范围非常大时(如1e18),要确保不会出现数值溢出
- 当输入为"0"或"1"时,需要正确处理
5.2 测试用例示例
java复制@Test
public void testSuperpalindromesInRange() {
Solution solution = new Solution();
assertEquals(4, solution.superpalindromesInRange("4", "1000"));
assertEquals(1, solution.superpalindromesInRange("1", "2"));
assertEquals(2, solution.superpalindromesInRange("1", "100"));
assertEquals(0, solution.superpalindromesInRange("10", "11"));
}
6. 性能优化技巧
6.1 提前终止循环
当生成的palindrome的平方已经超过右边界R时,可以立即终止循环,因为后续生成的palindrome会更大。
6.2 数学优化
观察到超级回文数非常稀少,我们可以预先计算出所有可能的超级回文数(在long类型范围内只有约70个),然后对于给定的范围,只需要在这些预计算的结果中进行二分查找即可。
6.3 并行处理
由于奇数和偶数长度的回文数生成是独立的,可以使用多线程并行处理这两部分计算。
7. 常见错误与调试技巧
7.1 数值溢出
在计算pal * pal时,可能会发生溢出。虽然题目中给出的范围在long的表示范围内,但在中间计算时仍需小心。
7.2 回文数生成错误
确保createPalindrome函数正确生成了所有可能的回文数。可以通过打印一些生成的palindrome来验证。
7.3 边界条件遗漏
特别注意left和right相等的情况,以及非常小的范围(如1-10)的情况。
8. 扩展思考
8.1 更高精度的实现
如果题目要求的范围超过了long的表示范围(如1e100),我们需要使用BigInteger类来处理大数运算。
8.2 其他进制下的超级回文数
这个问题可以扩展到其他进制(如二进制、十六进制)下的超级回文数查找,思路类似但实现细节会有所不同。
8.3 数学性质研究
超级回文数在数学上有一些有趣的性质,比如它们的分布规律、生成公式等,值得深入研究。
在实际面试中,这道题考察的不仅是编码能力,更重要的是对问题的分析和优化能力。从暴力解法出发,逐步优化到高效解法,并能够清楚地解释每一步优化的理由和效果,这是面试官最看重的。
