问题:
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[]>() { 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; } }