Leetcode0479. 最大回文数乘积(difficult)

目录

1. 题目描述

2. 解题分析

2.1 回文数的构造

2.2 判断是否有n位数因子 

3. 代码实现


1. 题目描述

给定一个整数 n ,返回 可表示为两个 n 位整数乘积的 最大回文整数 。因为答案可能非常大,所以返回它对 1337 取余

示例 1:

输入:n = 2
输出:987
解释:99 x 91 = 9009, 9009 % 1337 = 987

示例 2:

输入: n = 1
输出: 9

提示:

  • 1 <= n <= 8

2. 解题分析

        简单的暴力肯定是不行的,需要进行一些数学上的分析对问题进行简化。

        考虑两个n位数的乘积的范围。

        最小值为10^{n-1} * 10^{n-1} = 10^{2n-2},为2n-1位数

        最大值为(10^n-1)^2 = 10^{2n} - 2\cdot 10^n + 1,为2n位数

        直观的做法是在这个范围内,从大到小搜索具有n位数因子的回文数:

  •         (1) 首先找这个范围内(从大到小)的回文数
  •         (2) 判断该回文数是否具有n位数的因子

2.1 回文数的构造

        由于回文数是对称的,可以利用这个对称特性进行回文的构造和遍历。但是,需要区分位数为偶数和奇数两种情况(如上所述,两个n位数的乘积最大为2n位数此为偶数,最小为2n-1位数此为奇数)。

        对于2n位数,由n位数的从大到小遍历得到前半部分(左半部分),然后倒序后得到后半部分,两者串接即得一个2n位的回文数。选择先遍历生成前半部分而不是后半部分是因为最高位数的特殊性(不能为0)。总共是10^{n} - 10^{n-1}个数。

        对于2n-1位数,由n-1位数的从大到小遍历得到前半部分(左半部分),然后倒序后得到后半部分,中间再插入任何一个数,即得一个(2n-1)位的回文数。总共也是10 \cdot (10^{n-1} - 10^{n-2} ) = 10^n - 10^{n-1}个数。

2.2 判断是否有n位数因子 

        第(2)步并不等同于素因子分解,因为题目并没有要求是两个素数的乘积。某回文数的素因数分解中某个子集的乘积成为n位数即可(相应的另一个互补子集的乘积也必然为n位数)。

        可以从10^n - 1逐个向下遍历检查是否整除以上构造出来的回文数p,遍历的下界定为sqrt(p)即可(这个道理跟素数判断时只需要从小到大判断到sqrt(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位数的情况。但是如果不能从数学上证明这一点,代码实现中这一点是不能省的。

        回到主目录:笨牛慢耕的Leetcode每日一解题解笔记(动态更新。。。)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

笨牛慢耕

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值