由两个n位数相乘得到的最大回文 Largest Palindrome Product

本文针对LeetCode上的最大回文乘积问题提供两种解决方案:一是构造回文并验证是否为两个n位数的乘积;二是找出两个n位数的乘积并判断其是否构成回文。通过具体代码实现,展示了如何高效地解决问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题:

Find the largest palindrome made from the product of two n-digit numbers.

Since the result could be very large, you should return the largest palindrome mod 1337.

Example:

Input: 2

Output: 987

Explanation: 99 x 91 = 9009, 9009 % 1337 = 987

Note:

The range of n is [1,8].

解决:

【题意】找到由两个n位数相乘得到的最大回文。由于返回的结果可能会很大,所以结果为乘积 % 1337。n的范围是[1,8]。https://leetcode.com/problems/largest-palindrome-product/discuss/96306

解决这个问题有两个方向:一是先构造回文,然后判断它是否可以写成两个n位数的乘积; 另一种是首先获得两位数字的乘积,然后判断乘积是否是回文

① 先构造回文,然后判断它是否可以写成两个n位数的乘积:

首先,需要考虑两个问题:

1. 如何构建回文并按降序排列(我们只需要最大回文数)。

2. 如何判断一个给定的回文数可以写成两个数的乘积。

对于第一个问题,我们需要知道每个回文中有多少位数。由于回文是两个n位数的乘积,所以它可以有2n或2n - 1位数。而且,由于我们只需要最大回文数,因此我们将首先考虑2n位的回文。这些回文可以分为数字相同的两部分(每部分n个):左和右。左边是右边的镜像,反之亦然。 因此,每个回文将完全由其左边或右边的部分决定。

需要注意的是,左边的部分是一个n位的数字,如果我们按照降序排列,最终的回文也会按降序排列。因此,我们可以从最大的n位数向最小的n位数遍历。对于每个数字,我们可以通过连接数字和镜像来构建回文。

对于第二个问题,即如何判断一个给定的回文数可以写成两个数的乘积。这本质上是“整数分解”问题。一种简单的方法是“尝试分割”算法,即测试每个n位数以查看它是否是回文数的因子,如果是,则另一个因子也是n位数字。

注意我们只考虑了2n位的回文。 幸运的是,对于每种情况(n = 1到8),我们能够找到至少一个可以分解成两个n位数字的回文,因此不需要检查那些具有2n - 1个数字的回文。O(10^n)

class Solution { //387ms
    public int largestPalindrome(int n) {
        if (n == 1) return 9;
        long max = (long) Math.pow(10,n) - 1;//n位数的最大回文,上界
        long min = max / 10;//下界
        for (long p = max;p > min;p --){//从大到小,构造回文
            long left = p;//最大回文的左半部分
            long right = 0;
            for (long i = p;i != 0;i /= 10){
                right = right * 10 + i % 10;
                left *= 10;
            }
            long palindrome = left + right;//构造回文序列
            for (long i = max;i > min;i --){
                long j = palindrome / i;
                if (j > i || j <= min) break;//如果另一个因子大于当前因子,或者不是n位数,则终止
                if (palindrome % i == 0) return (int)(palindrome % 1337);//如果当前数是一个因子,则找到了满足条件的最大回文
            }
        }
        return 9;//n = 1时
    }
}

② 根据上面的分析,我们只需要关注最大回文数即可,所以可以从9开始检查回文。如果没找到,则从8,7,6,...开始检查。如果这个回文数是以9开始的,则其结尾也应该是9。如果回文数是两个n位数的乘积,则这两个数应该以1,3,7,9结尾。对于其他的情况也是如此。

对于每个n来说,至少存在一个具有2n位的回文数,它以数字9开始,可以写成两个n位数的乘积。

经过分析,有如下结论:对于每个n,存在至少两个n位数字num1和num2,10 ^ n-10 ^ m <= num1,num2 <= 10 ^ n -1和m = [ n + 1)/ 2],其乘积将是回文数

先得到两个n位数的乘积,再判断乘积是否是回文。

与第一种方法类似,我们需要考虑以下两个问题:

1. 如何构建乘积并降序排列。

2. 如何判断给定的乘积是回文数。

第二个问题很简单,只需将乘积倒过来,并判断它是否与原来的乘积相同。 但是,第一个问题有点困难。 获得乘积是一件容易的事。 困难的部分是如何按降序排列。

首先我们需要确定候选乘积。 从上面的分析看,我们只考虑在[10 ^ n - 10 ^ m,10 ^ n - 1]范围内两个n位数字获得的乘积。其次,我们可以使用PriorityQueue以降序提取这些候选乘积。

然而,当n = 8时,上面候选乘积的总数仍然很多。所以我们需要进一步的修剪:

首先,由于乘积以数字9结尾,所以两个数字必须以数字1,3,7或9结尾。
其次,为了避免重复,第一个数字不会小于第二个。
第三,对于第一个因子固定的所有乘积,没有必要一次把所有乘积都记下来,而是可以考虑将第二个因子按顺序递减来遍历。
最后我们将这两个因子存储在PriorityQueue中,同时根据他们的乘积提取它们。

class Solution {//275ms
    public int largestPalindrome(int n) {
        //两个因子的范围[10^n - 10^m, 10^n - 1],m = [(n+1)/2]
        int max = (int) Math.pow(10,n) - 1;
        int min = max - (int) Math.pow(10,(n + 1) >> 1);
        Comparator<int[]> cmp = new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {//将乘积按降序排列
                return Long.compare((long) o2[0] * o2[1],(long) o1[0] * o1[1]);
            }
        };
        PriorityQueue<int[]> priorityQueue = new PriorityQueue<>(max - min,cmp);
        for (int i = max;i > min;i --){
            int tail = i % 10;
            if (tail == 3 || tail == 7){
                priorityQueue.offer(new int[]{i,i});
            }else if (tail == 1){
                priorityQueue.offer(new int[]{i,i - 2});
            }else if (tail == 9){
                priorityQueue.offer(new int[]{i,i - 8});
            }
        }
        while (! priorityQueue.isEmpty()){
            int[] tmp = priorityQueue.poll();
            long palindrome = (long) tmp[0] * tmp[1];
            if (isPalindrome(palindrome)){
                return (int)(palindrome % 1337);
            }
            if (tmp[1] > min){
                tmp[1] -= 10;
                priorityQueue.offer(tmp);
            }
        }
        return 0;
    }
    public boolean isPalindrome(long x){
        long reverse = 0;
        for (long tmp = x; tmp != 0;tmp /= 10){
            reverse = reverse * 10 + tmp % 10;
        }
        return reverse == x;
    }
}

转载于:https://my.oschina.net/liyurong/blog/1603314

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值