Loading...

超级回文数问题

在这里插入图片描述

问题分析

如果直接枚举范围 [left, right] 中的每个数字,判断它是否为回文数以及其平方根是否为回文数,时间复杂度会非常

但是,如果一个数 x 是超级回文数,那么 x 的平方根必定是回文数。

因此,可以转换思路,不枚举 x,而是枚举 x 的平方根。

由于 x 最大为 $10^{18}$,那么 x 的平方根最大为 $10^9$。

这里还有一个优化:

回文数具有对称性,不需要直接枚举所有 $10^{9}$以内的数字来判断哪些是回文数。

可以用一个"种子"数来构造回文数。

具体来说的话就是:

一个 9 位的回文数(如 123454321)可以由前 5 位(12345)确定,因为后 4 位是前 4 位的镜像;

一个 8 位的回文数(如 12344321)可以由前 4 位(1234)确定。

因此,对于最大 $10^9$(10 位数)的回文数,只需要枚举 1 到 $10^5$(5 位数)的种子,就能构造出所有可能的回文数。

每个种子生成两种回文数:

偶数长度(种子完整镜像,如 123→123321)和奇数长度(种子去掉最后一位后镜像,如 123→12321)。

这样,问题规模就从枚举 $10^9$ 个数降到只需枚举 $10^5$ 个种子,大幅降低了时间复杂度。

求解思路

通过枚举种子数来生成回文数,每个种子可以生成两种回文数:奇数长度的回文数和偶数长度的回文数。

对于每个生成的回文数 num,计算其平方 num²,判断 num² 是否在给定范围 [left, right] 内,并且 num² 本身也是回文数。

如果满足条件,计数器加一。从种子 1 开始递增枚举,直到生成的回文数 num 超过了 right 的平方根为止。

完整代码实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
public class Solution {
    
    public static int superpalindromesInRange(String left, String right) {
        long l = Long.valueOf(left);
        long r = Long.valueOf(right);
        
        // 计算右边界的平方根,作为枚举回文数的上限
        long limit = (long) Math.sqrt((double) r);
        
        // seed: 种子数,用于生成回文数
        // 从1开始枚举,每个种子可以生成奇数长度和偶数长度两种回文数
        long seed = 1;
        
        // num: 根据种子生成的回文数(即超级回文数的平方根)
        long num = 0;
        
        // 统计符合条件的超级回文数数量
        int ans = 0;
        
        do {
            // 用seed生成偶数长度的回文数
            // 例如: 123 -> 123321
            num = evenEnlarge(seed);
            // 检查 num² 是否在范围内且为回文数
            if (check(num * num, l, r)) {
                ans++;
            }
            
            // 用seed生成奇数长度的回文数
            // 例如: 123 -> 12321
            num = oddEnlarge(seed);
            // 检查 num² 是否在范围内且为回文数
            if (check(num * num, l, r)) {
                ans++;
            }
            
            // 种子递增,继续枚举
            seed++;
        } while (num < limit); // 当生成的回文数超过limit时停止
        
        return ans;
    }
    
    public static long evenEnlarge(long seed) {
        long ans = seed;
        // 将seed的每一位依次添加到ans末尾
        while (seed != 0) {
            ans = ans * 10 + seed % 10;  // 取seed最后一位拼接到ans
            seed /= 10;  // 去掉seed的最后一位
        }
        return ans;
    }
    
    public static long oddEnlarge(long seed) {
        long ans = seed;
        seed /= 10;  // 先去掉最后一位,避免中间数字重复
        // 将剩余部分翻转后拼接
        while (seed != 0) {
            ans = ans * 10 + seed % 10;
            seed /= 10;
        }
        return ans;
    }
    
    public static boolean check(long ans, long l, long r) {
        return ans >= l && ans <= r && isPalindrome(ans);
    }
    
    public static boolean isPalindrome(long num) {
        // offset用于定位最高位,例如52725,offset为10000
        long offset = 1;
        // 计算offset的值,使其等于10^(位数-1)
        // 注意这里用 num/offset >= 10 而不是 num >= offset*10,防止溢出
        while (num / offset >= 10) {
            offset *= 10;
        }
        
        // 首尾对比判断是否回文
        while (num != 0) {
            // 比较最高位和最低位
            if (num / offset != num % 10) {
                return false;
            }
            // 去掉最高位和最低位,继续判断内部数字
            num = (num % offset) / 10;  // 去掉最高位后再去掉最低位
            offset /= 100;  // 因为去掉了两位,所以offset除以100
        }
        
        return true;
    }
}

如果觉得有帮助,欢迎点赞、关注、转发~

最后更新于 2026-04-05 17:35:33
Code Road Record