算法总结9 高阶DP

数位DP

2801. 统计范围内的步进数字数目

以该题作为模板,完成后面的所有题。

class Solution:
    def countSteppingNumbers(self, low: str, high: str) -> int:
        MOD = 10 ** 9 + 7
        def calc(s):
            @cache # 记忆化搜索
            def dfs(i, pre, is_limit, is_num):
                if i == len(s):
                    return int(is_num)
                res = 0
                # 1. 直接跳过当前数位
                if not is_num:  # 可以跳过当前数位
                    res = dfs(i + 1, pre, False, False)
                # 2. 不跳过,那么low,up,is_limit,is_num都要做相应的变化
                # 当前位数遍历选值
                
                low = 0 if is_num else 1  # 如果前面没有填数字,必须从 1 开始(因为不能有前导零)
                up = int(s[i]) if is_limit else 9  # 如果前面填的数字都和 s 的一样,那么这一位至多填 s[i](否则就会超过 s )
                for d in range(low, up + 1):  # 枚举要填入的数字 d
                    if not is_num or abs(d - pre) == 1:  # 第一位数字随便填,其余必须相差 1
                        res += dfs(i + 1, d, is_limit and d == up, True)


                return res % MOD         
            return dfs(0, 0, True, False)
        return (calc(high)-calc(str(int(low)-1))) % MOD

参考


233. 数字 1 的个数

class Solution:
    def countDigitOne(self, n: int) -> int:
        def cal(s):
            @cache
            def dfs(i, is_limit, is_num, cnt):
                if i == len(s):
                    return cnt
                res = 0
                if not is_num:
                    res  = dfs(i+1, False, False, cnt)
                low = 0 if is_num else 1
                up = int(s[i]) if is_limit else 9
                for d in range(low, up+1):
                    res += dfs(i+1, is_limit and d==up, True, cnt+(d==1))
                return res
            return dfs(0, True, False, 0)
        return cal(str(n))        

参考


面试题 17.06. 2出现的次数

面试题 17.06. 2出现的次数

class Solution:
    def numberOf2sInRange(self, n: int) -> int:
        s = str(n)
        length = len(s)
        @cache
        def dfs(i, cnt, is_limit, is_num):
            if i == length:
                return cnt
            res = 0
            if not is_num:
                res = dfs(i+1, cnt, False, False)
            low = 0 if is_num else 1
            high = int(s[i]) if is_limit else 9

            for d in range(low, high+1):
                res += dfs(i+1, cnt+(d==2), is_limit and (d==high), True)
            return res
        return dfs(0, 0, True, False)

参考


600. 不含连续1的非负整数

600. 不含连续1的非负整数

class Solution:
    def findIntegers(self, n: int) -> int:
        s = bin(n)[2:]
        length = len(s)
        @cache
        def dfs(i, pre, is_limit, is_num):
            if i == length:
                return int(is_num)
            res = 0
            if not is_num:
                res = dfs(i+1, pre, False, False)

            low = 0 if is_num else 1
            high = int(s[i]) if is_limit else 1
            for d in range(low, high+1):
                if not (d==pre==1):
                    res += dfs(i+1, d, is_limit and (d==high), True)
            return res
        return dfs(0, 0, True, False)+1

参考


902. 最大为 N 的数字组合

902. 最大为 N 的数字组合

class Solution:
    def atMostNGivenDigitSet(self, digits: List[str], limit: int) -> int:
        s = str(limit)
        n = len(s)
        @cache
        def dfs(i, is_limit, is_num):
            if i==n:
                return int(is_num)
            res = 0
            
            if not is_num:
                res = dfs(i+1, False, False)
            up = s[i] if is_limit else '9'
            for d  in digits:
                if d<=up:
                    res += dfs(i+1, is_limit and (d==up), True)
            return res
        return dfs(0, True, False)

参考


1012. 至少有 1 位重复的数字

1012. 至少有 1 位重复的数字
正难则反

class Solution:
    def numDupDigitsAtMostN(self, n: int) -> int:
        s = str(n)
        pre = set()
        @cache
        def dfs(i, rep,  is_limit, is_num):
            if i==len(s):
                return int(rep and is_num)
            res = 0
            if not is_num:
                res  = dfs(i+1, rep, False, False)
            low = 0 if is_num else 1
            high = int(s[i]) if is_limit else 9
            for d in range(low, high+1):
                rep = d in pre
                pre.add(d)
                res += dfs(i+1, rep, is_limit and (d==high), True)
                if not rep:
                    pre.remove(d)
            return res
        return dfs(0, True, False)

参考

1067. 范围内的数字计数

1397. 找到所有好字符串