数位dp,LeetCode 2376 统计特殊整数

目录

一、题目

1、题目描述

2、接口描述

python3

cpp

C#

3、原题链接

二、解题报告

1、思路分析

2、复杂度

3、代码详解

python3

cpp

C#


一、题目

1、题目描述

如果一个正整数每一个数位都是 互不相同 的,我们称它是 特殊整数 。

给你一个  整数 n ,请你返回区间 [1, n] 之间特殊整数的数目。

2、接口描述

python3
 ​
class Solution:
    def countSpecialNumbers(self, n: int) -> int:
cpp
 ​
class Solution {
public:
    int countSpecialNumbers(int n) {
        
    }
};
C#
 ​
public class Solution {
    public int CountSpecialNumbers(int n) {

    }
}

3、原题链接

2376. Count Special Integers


二、解题报告

1、思路分析

关于数位dp:数位dp详解,记忆化搜索,递推,OJ精讲_数位dp记忆化搜索-CSDN博客

数位dp很多时候都是板子题,只需修改记忆化搜索的参数和状态获取

这里如果使用记忆化的做法我们需要:

前面填过的数的集合pre(二进制表示集合),前缀是否小于n的前缀lim,是否前导零 zero

然后下面就是板子做法了

这里额外提一下递推做法:

记忆化的做法实际上是在走搜索树,最右侧的一条路径 lim 始终为false,其本质是等于n的情况,对于其它路径其实可以组合数学求解

对于位数小于n的数,不妨设位数为i,那么方案数为 9 * fac(9) / fac(10 - i)

对于位数等于n的数,我们从高位试填(走搜索树上最右侧路径),用 vis 数组记录用过的数字

对于第 i 位,假如有 cnt 个数字未使用

试着填 第 i 位填 小于 d[i] 的情况,方案为 cnt * fac(9 - i, m - i - 1)

然后判断 该位能否填 d[i],即 vis[d[i]] 是否出现?

出现的话,break

否则,vis[d[i]] = true

对于最右侧路径对应n只会有1的贡献,我们只需在结束的时候判断是否走完了路径来使得答案+1即可

2、复杂度

时间复杂度: O(log^2 n)空间复杂度:O(log(n))

python3 记忆化 时间复杂度:O(mU2^U), U = 10

3、代码详解

python3
 ​
class Solution:
    def countSpecialNumbers(self, n: int) -> int:
        d = []
        t = n
        while t:
            d.append(t % 10)
            t //= 10

        @cache
        def dfs(n: int, pre: int, lim: bool, zero: bool) -> int:
            if n < 0:
                return not zero
            top = 9 if lim else d[n]
            res = 0
            for i in range(top + 1):
                if (pre >> i) & 1: continue
                nxt = pre | (1 << i)
                if i == 0 and zero:
                    nxt &= nxt - 1
                res += dfs(n - 1, nxt, lim or i < top, zero and i == 0)

            return res

        dfs.cache_clear()

        return dfs(len(d) - 1, 0, False, True)
cpp
 ​
int fac[10];
auto init = []{
    fac[0] = 1;
    for (int i = 1; i < 10; ++ i)
        fac[i] = fac[i - 1] * i;
    return 0;
}();

int getPer(int n, int m) {
    return fac[n] / fac[n - m];
}

class Solution {
public:
    int countSpecialNumbers(int n) {
        std::string s = std::to_string(n);
        int m = s.size();
        int res = 0;
        for (int i = 0; i + 1 < m; ++ i) res += 9 * getPer(9, i);
        std::vector<bool> vis(10);
        int i = 0;
        for (; i < m; ++ i) {
            int v = s[i] ^ 48;
            int cnt = 0;
            for (int j = i ? 0 : 1; j < v; ++ j) 
                if (!vis[j]) 
                    ++ cnt;
            res += cnt * getPer(9 - i, m - i - 1);
            if (vis[v]) break;
            else vis[v] = true;
        }
        if (i == m) ++ res;
        return res;
    }
};
C#
 ​
    public class Solution
    {
        static int[] fac;
        static Solution()
        {
            fac = new int[10];
            fac[0] = 1;
            for (int i = 1; i < 10; ++ i) fac[i] = i * fac[i - 1];
        }

        static int getPer(int n, int k)
        {
            return fac[n] / fac[n - k];
        }

        public int CountSpecialNumbers(int n)
        {
            string s = n.ToString();
            int m = s.Length, res = 0;
            bool[] vis = new bool[10];
            int i = 0;
            for (i = 0; i + 1 < m; ++i) res += 9 * getPer(9, i);
            for (i = 0; i < m; ++ i)
            {
                int v = s[i] ^ 48;
                int cnt = 0;
                for (int j = i > 0 ? 0 : 1; j < v; ++j) {
                    if (!vis[j])
                        ++cnt;
                }
                res += cnt * getPer(9 - i, m - i - 1);
                if (vis[v]) break;
                else vis[v] = true;
            }
            if (i == m) ++res;
            return res;
        }
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

EQUINOX1

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

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

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

打赏作者

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

抵扣说明:

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

余额充值