leetcode每日一题:统计好整数的数目
题目
给你两个 正 整数 n
和 k
。
如果一个整数 x
满足以下条件,那么它被称为 k 回文 整数 。
-
x
是一个 回文整数 。 -
x
能被k
整除。
如果一个整数的数位重新排列后能得到一个 k 回文整数 ,那么我们称这个整数为 好 整数。比方说,k = 2
,那么 2020 可以重新排列得到 2002 ,2002 是一个 k 回文串,所以 2020 是一个好整数。而 1010 无法重新排列数位得到一个 k 回文整数。
请你返回 n
个数位的整数中,有多少个 好 整数。
注意 ,任何整数在重新排列数位之前或者之后 都不能 有前导 0 。比方说 1010 不能重排列得到 101 。
示例 1:
输入:n = 3, k = 5
输出:27
解释:
部分好整数如下:
-
551 ,因为它可以重排列得到 515 。
-
525 ,因为它已经是一个 k 回文整数。
示例 2:
输入:n = 1, k = 4
输出:2
解释:
两个好整数分别是 4 和 8 。
示例 3:
输入:n = 5, k = 6
输出:2468
提示:
-
1 <= n <= 10
-
1 <= k <= 9
思路
本题其实可以分成2个部分:1、求出长度为n且可以被k的回文数字;2、计算第1步求出来数字重新排列后的所有排列可能。
对于第1步,如果直接遍历判断数字是否是回文肯定会超时,我们尝试构造回文数字。由于回文数字的特点,前半部分和后半部分是对称的,根据长度的奇偶性不同,我们可以分成2种情况:
-
n是奇数,前(n-1)/2 和 后(n-1)/2是对称的,最中间1位是任意的
-
n是偶数,前 n/2 和后 n/2 是对称的
上述2种情况,在构造的时候,可以统一:我们枚举左半部分的开始,1开头,后面跟(n-1)/2个0,如果n是奇数,后半部分是左半部分反转后去掉首字符,如果是偶数,后半部分直接就是左半部分反转。构造出回文后,我们再判断1次是否可以被k整除,排除掉不能整除的回文数。
接下来,对于每一个长度为n的整除k的回文,我们要计算排列组合。对于相同数字组合的回文,我们要去重,避免重复计算。例如,n = 4,k = 2,此时 2442 和 4224 都是符合条件的,但是计算的时候,我们只要计算1个数的所有排列即可。去重的方法可以把这个数字组成的字符数组进行排序,排序后的字符串唯一即可。
对于一个给定的数字,求出全排列的数量,在另外一题有推导过,这里直接使用那一题的结论。
代码
private static final int[] FAC; static { FAC = new int[11]; FAC[0] = 1; for (int i = 1; i < FAC.length; i++) { FAC[i] = FAC[i - 1] * i; } } public long countGoodIntegers(int n, int k) { long ans = 0; Set<String> seen = new HashSet<>(); int begin = (int) Math.pow(10, (n-1)/2); for (int left = begin; left < begin * 10; left++) { String leftStr = String.valueOf(left); String full = leftStr + new StringBuilder(leftStr).reverse().substring(n % 2); if (Long.parseLong(full) % k != 0) { continue; } char[] chars = full.toCharArray(); Arrays.sort(chars); if (seen.contains(String.valueOf(chars))) { continue; } ans += cnt(chars); seen.add(String.valueOf(chars)); } return ans; } private long cnt(char[] chars) { int[] cnt = new int[10]; for (char c : chars) { cnt[c - '0']++; } int n = chars.length; long ans = (long) (n - cnt[0]) * FAC[n - 1]; for (int c : cnt) { ans /= FAC[c]; } return ans; }