目录
1. 题目描述
给定一个整数 n ,返回 可表示为两个 n
位整数乘积的 最大回文整数 。因为答案可能非常大,所以返回它对 1337
取余 。
示例 1:
输入:n = 2 输出:987 解释:99 x 91 = 9009, 9009 % 1337 = 987
示例 2:
输入: n = 1 输出: 9
提示:
1 <= n <= 8
2. 解题分析
简单的暴力肯定是不行的,需要进行一些数学上的分析对问题进行简化。
考虑两个n位数的乘积的范围。
最小值为,为2n-1位数
最大值为,为2n位数
直观的做法是在这个范围内,从大到小搜索具有n位数因子的回文数:
- (1) 首先找这个范围内(从大到小)的回文数
- (2) 判断该回文数是否具有n位数的因子
2.1 回文数的构造
由于回文数是对称的,可以利用这个对称特性进行回文的构造和遍历。但是,需要区分位数为偶数和奇数两种情况(如上所述,两个n位数的乘积最大为2n位数此为偶数,最小为2n-1位数此为奇数)。
对于2n位数,由n位数的从大到小遍历得到前半部分(左半部分),然后倒序后得到后半部分,两者串接即得一个2n位的回文数。选择先遍历生成前半部分而不是后半部分是因为最高位数的特殊性(不能为0)。总共是个数。
对于2n-1位数,由n-1位数的从大到小遍历得到前半部分(左半部分),然后倒序后得到后半部分,中间再插入任何一个数,即得一个(2n-1)位的回文数。总共也是个数。
2.2 判断是否有n位数因子
第(2)步并不等同于素因子分解,因为题目并没有要求是两个素数的乘积。某回文数的素因数分解中某个子集的乘积成为n位数即可(相应的另一个互补子集的乘积也必然为n位数)。
可以从逐个向下遍历检查是否整除以上构造出来的回文数p,遍历的下界定为
即可(这个道理跟素数判断时只需要从小到大判断到
的道理相同,此处不再赘述) 。
3. 代码实现
import time
class Solution:
def largestPalindrome(self, n: int) -> int:
if n == 1:
return 9
# case1: 2n digits number
upper = 10 ** n - 1
for left in range(upper, upper // 10, -1):
# The left(upper) half of the palindrome
x = str(left)
p = int(x + x[::-1])
x = upper
# while x*x >= p and x >= 10 ** (n-1):
while x*x >= p:
if p%x == 0:
print('The answer is 2n digits number')
return p%1337
x -= 1
# case2: 2n-1 digits number
upper1 = 10 ** (n-1) - 1
for left in range(upper1, upper1 // 10, -1):
# The left(upper) half of the palindrome
x = str(left)
for k in range(10):
p = int(x + str(k) + x[::-1])
x = upper
# while x*x >= p and x >= 10 ** (n-1):
while x*x >= p:
if p%x == 0:
print('The answer is (2n-1) digits number')
return p%1337
x -= 1
if __name__ == "__main__":
sln = Solution()
for k in range(1,9):
tstart = time.time()
ans = sln.largestPalindrome(k)
tstop = time.time()
print('k={0}, ans={1}, tcost={2:4.2f}'.format(k,ans,tstop-tstart))
执行用时:2964 ms, 在所有 Python3 提交中击败了19.74%的用户
内存消耗:15.1 MB, 在所有 Python3 提交中击败了6.58%的用户
以上代码中,如果用条件“while x*x >= p and x >= 10 ** (n-1):”更为严谨,但是耗时翻了1倍以上,而结果并没有差异。所以如果能从数学上证明“x >= 10 ** (n-1)”的条件是不需要的的话就perfect了。
进一步,根据打印信息看到的结果表明,最终结果都是2n位数,没有2n-1位数的情况。但是如果不能从数学上证明这一点,代码实现中这一点是不能省的。