普通视图

发现新文章,点击刷新页面。
今天 — 2025年4月12日LeetCode 每日一题题解

每日一题-统计好整数的数目🔴

2025年4月12日 00:00

给你两个  整数 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

回溯+全排列模板题

作者 sierpinski-f
2024年9月1日 09:42

Problem: 3272. 统计好整数的数目

[TOC]

思路

先回溯找到所有可能的回文串,然后统计每个回文串的排列个数,注意排列不能0开头

Code

###Python3

@cache
def fac(x):
    if x == 0:
        return 1
    return x * fac(x-1)

class Solution:
    def countGoodIntegers(self, n: int, k: int) -> int:
        pow10 = [1] * n
        for i in range(1, n):
            pow10[i] = pow10[i - 1] * 10 % k

        st = set()
        m = (n + 1) // 2
        res = []
        def dfs(i: int, j: int) -> None:
            if i == m:
                if j == 0:
                    cur = res.copy()
                    cur += cur[:n // 2]
                    cur.sort()
                    st.add("".join(cur))

                return
            
            mn = 1 if i == 0 else 0
            for d in range(mn, 10):  
                if n % 2 and i == m - 1:  # 正中间
                    j2 = (j + d * pow10[i]) % k
                else:
                    j2 = (j + d * (pow10[i] + pow10[-1 - i])) % k
                res.append(str(d))
                dfs(i + 1, j2)
                res.pop()
            
        dfs(0, 0)
        ans = 0
        for x in st:
            cnt = Counter(x)
            f = fac(n)
            for v in cnt.values():
                f //= fac(v)
            
            # 减去0开头的
            if cnt['0'] > 0:
                f1 = fac(n-1) 
                for k, v in cnt.items():
                    if k == '0':
                        f1 //= fac(v - 1)
                    else:
                        f1 //= fac(v)
                f -= f1 
            ans += f 
        
        return ans

枚举所有回文数+组合数学(Python/Java/C++/Go)

作者 endlesscheng
2024年9月1日 08:48

考虑枚举所有长为 $n$ 的回文数。

首先,知道了回文数的左半边,就知道了回文数的右半边,所以可以枚举回文数的左半边。

设 $m = \left\lfloor\dfrac{n-1}{2}\right\rfloor$,设 $\textit{base} = 10^m$。

在 $[\textit{base}, 10\cdot\textit{base})$ 范围内枚举所有长为 $n$ 的回文数的左半边。

如果回文数 $x$ 能被 $k$ 整除,那么接下来需要解决的问题有两个:

  1. 计算 $x$ 有多少个不同的排列。
  2. 不能重复统计。

为了保证不重复统计,可以像 49. 字母异位词分组 那样,把 $x$ 的十进制字符串 $s$ 排序,如果之前遇到过同样的字符串 $t$,那么 $s$ 生成的所有排列,$t$ 也能生成。用哈希表记录排序后的字符串,如果 $s$ 排序后在哈希表中,那么就跳过。

下面是组合数学时间。

本质上计算的是「有重复元素的排列个数」。

统计 $s$ 中的每个数字的出现次数 $\textit{cnt}$。

先填最高位。由于不能有前导零,最高位可以填的数有 $n-\textit{cnt}_0$ 个。其余 $n-1$ 个数随便排,有 $(n-1)!$ 种方案。

当然,这里面有重复的,例如 $x=34543$,其中两个 $3$ 和两个 $4$ 的排列就是重复的,由于这两个 $3$ 无法区分,两个 $4$ 无法区分,方案数要除以 $2!2!$。

综上,排列个数为

$$
\dfrac{(n-\textit{cnt}0)\cdot (n-1)!}{\prod\limits{i=0}^{9}\textit{cnt}_i!}
$$

加入答案。

具体请看 视频讲解 第三题,欢迎点赞关注~

###py

class Solution:
    def countGoodIntegers(self, n: int, k: int) -> int:
        fac = [factorial(i) for i in range(n + 1)]
        ans = 0
        vis = set()
        base = 10 ** ((n - 1) // 2)
        for i in range(base, base * 10):  # 枚举回文数左半边
            s = str(i)
            s += s[::-1][n % 2:]
            if int(s) % k:  # 回文数不能被 k 整除
                continue

            sorted_s = ''.join(sorted(s))
            if sorted_s in vis:  # 不能重复统计
                continue
            vis.add(sorted_s)

            cnt = Counter(sorted_s)
            res = (n - cnt['0']) * fac[n - 1]
            for c in cnt.values():
                res //= fac[c]
            ans += res
        return ans

###java

class Solution {
    public long countGoodIntegers(int n, int k) {
        int[] factorial = new int[n + 1];
        factorial[0] = 1;
        for (int i = 1; i <= n; i++) {
            factorial[i] = factorial[i - 1] * i;
        }

        long ans = 0;
        Set<String> vis = new HashSet<>();
        int base = (int) Math.pow(10, (n - 1) / 2);
        for (int i = base; i < base * 10; i++) { // 枚举回文数左半边
            String s = Integer.toString(i);
            s += new StringBuilder(s).reverse().substring(n % 2);
            if (Long.parseLong(s) % k > 0) { // 回文数不能被 k 整除
                continue;
            }

            char[] sortedS = s.toCharArray();
            Arrays.sort(sortedS);
            if (!vis.add(new String(sortedS))) { // 不能重复统计
                continue;
            }

            int[] cnt = new int[10];
            for (char c : sortedS) {
                cnt[c - '0']++;
            }
            int res = (n - cnt[0]) * factorial[n - 1];
            for (int c : cnt) {
                res /= factorial[c];
            }
            ans += res;
        }
        return ans;
    }
}

###cpp

class Solution {
public:
    long long countGoodIntegers(int n, int k) {
        vector<int> factorial(n + 1);
        factorial[0] = 1;
        for (int i = 1; i <= n; i++) {
            factorial[i] = factorial[i - 1] * i;
        }

        long long ans = 0;
        unordered_set<string> vis;
        int base = pow(10, (n - 1) / 2);
        for (int i = base; i < base * 10; i++) { // 枚举回文数左半边
            string s = to_string(i);
            s += string(s.rbegin() + (n % 2), s.rend());
            if (stoll(s) % k) { // 回文数不能被 k 整除
                continue;
            }

            ranges::sort(s);
            if (!vis.insert(s).second) { // 不能重复统计
                continue;
            }

            int cnt[10]{};
            for (char c : s) {
                cnt[c - '0']++;
            }
            int res = (n - cnt[0]) * factorial[n - 1];
            for (int c : cnt) {
                res /= factorial[c];
            }
            ans += res;
        }
        return ans;
    }
};

###go

func countGoodIntegers(n, k int) (ans int64) {
factorial := make([]int, n+1)
factorial[0] = 1
for i := 1; i <= n; i++ {
factorial[i] = factorial[i-1] * i
}

vis := map[string]bool{}
base := int(math.Pow10((n - 1) / 2))
for i := base; i < base*10; i++ { // 枚举回文数左半边
x := i
t := i
if n%2 > 0 {
t /= 10
}
for ; t > 0; t /= 10 {
x = x*10 + t%10
}
if x%k > 0 { // 回文数不能被 k 整除
continue
}

bs := []byte(strconv.Itoa(x))
slices.Sort(bs)
s := string(bs)
if vis[s] { // 不能重复统计
continue
}
vis[s] = true

cnt := [10]int{}
for _, c := range bs {
cnt[c-'0']++
}
res := (n - cnt[0]) * factorial[n-1]
for _, c := range cnt {
res /= factorial[c]
}
ans += int64(res)
}
return
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(10^m\cdot n\log n)$,其中 $m = \left\lfloor\dfrac{n-1}{2}\right\rfloor$。
  • 空间复杂度:$\mathcal{O}(10^m\cdot n)$。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
  7. 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

枚举 + 组合数学

作者 mipha-2022
2024年9月1日 00:15

Problem: 3272. 统计好整数的数目

[TOC]

思路

枚举

回文,很容易想到折半枚举,n最大10位,折半枚举最大就5位,所以:

  • 枚举回文左边
  • 镜像得到右边
  • 拼接成完整数字,再判断该数字能否被k整除
        # 枚举
        n_half = (n+1) // 2
        start = 10**(n_half-1)
        end = 10**n_half
        dt = set()
        for num in range(start,end,1):
            l = str(num)
            if n&1:
                r = l[:-1][::-1]
            else:
                r = l[::-1]
            
            s = l+r
            if int(s) % k == 0:
                # 去重
                dt.add(''.join(sorted(s)))

组合数学

将第一步得到的数字打乱,看可以得到多少个没有前缀0的整数。

  • 枚举非0头位
  • 剩下的位置塞入其它数字
        res = 0
        # 组合数学
        for s in dt:
            # 计数
            cnt = Counter(s)

            # 开头非0
            for w in '123456789':
                if cnt[w]:
                    cnt[w] -= 1
                    # 组合数学
                    t = 1
                    rest = n - 1
                    for lw in '0123456789':
                        # 剩余位中插入数字
                        t *= comb(rest,cnt[lw])
                        rest -= cnt[lw]
                    # 回溯
                    cnt[w] += 1
                    res += t

        return res

更多题目模板总结,请参考2023年度总结与题目分享

Code

###Python3

class Solution:
    def countGoodIntegers(self, n: int, k: int) -> int:
        '''
        打表
        折半 + 组合数字
        '''
        
        # 枚举
        n_half = (n+1) // 2
        start = 10**(n_half-1)
        end = 10**n_half
        dt = set()
        for num in range(start,end,1):
            l = str(num)
            if n&1:
                r = l[:-1][::-1]
            else:
                r = l[::-1]
            
            s = l+r
            if int(s) % k == 0:
                # 去重
                dt.add(''.join(sorted(s)))
        
        res = 0
        # 组合数学
        for s in dt:
            # 计数
            cnt = Counter(s)

            # 开头非0
            for w in '123456789':
                if cnt[w]:
                    cnt[w] -= 1
                    # 组合数学
                    t = 1
                    rest = n - 1
                    for lw in '0123456789':
                        if cnt[lw]:
                            t *= comb(rest,cnt[lw])
                            rest -= cnt[lw]
                    # 回溯
                    cnt[w] += 1
                    res += t

        return res
❌
❌