leetcode 902. 最大为 N 的数字组合

120 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第21天,点击查看活动详情

1. 题目与解析

给定一个按 非递减顺序 排列的数字数组 digits 。你可以用任意次数 digits[i] 来写的数字。例如,如果 digits = ['1','3','5'],我们可以写数字,如 '13''551', 和 '1351315'

返回 可以生成的小于或等于给定整数 n 的正整数的个数 。

输入: digits = ["1","3","5","7"], n = 100

输出: 20

解释:

可写出的 20 个数字是:

1, 3, 5, 7, 11, 13, 15, 17, 31, 33, 35, 37, 51, 53, 55, 57, 71, 73, 75, 77.

输入: digits = ["1","4","9"], n = 1000000000

输出: 29523

解释:

我们可以写 3 个一位数字,9 个两位数字,27 个三位数字,

81 个四位数字,243 个五位数字,729 个六位数字,

2187 个七位数字,6561 个八位数字和 19683 个九位数字。

总共,可以使用D中的数字写出 29523 个整数。

输入: digits = ["7"], n = 8

输出: 1

这道题是数位DP的典型题目。

数位DP是指求小于等于数字n的所有非负整数中符合某个特征的数字的个数,这种题目都有两个特点:

  • 需要遍历小于等于n的所有数字;
  • 需要针对某个计数方式进行计数。

在进行此类题目的解答时,我们可以使用记忆化存储的模板来解题:

1 @cache
2 def f(i: int, mask: int, isLimit: bool, isNum: bool) -> int:
3     if i == len(s):
4         return `end_state`
5     res = 0
6     if not isNum:
7         res += f(i+1, mask, False, False)
8     up = int(s[i]) if isLimit else 9
9     for num in range(1 - int(isNum), up + 1):
10        res += f(i+1, mask+int(num == 1), isLimit and num == up, True)
11    return res
复制代码
  • i:目前遍历到的位数;

  • mask:通过掩码的形式传递一个状态(计数或者是已经使用过的数字等等);

  • isLinut:前i-1位是否与n相同;

  • isNum:前i-1位是否全都是前置0。

  • @cache是python装饰器,用于记忆化存储,其他语言可以使用一个二维dp数组进行记忆化存储;

  • 第3-4行是模板的截止条件,即遍历过所有数字之后,返回截止值;

  • 第6-7行是针对前几位都是前置0的情况,在第i位继续填写前置0进行遍历;

  • 第8行是计算第i位能填写哪几个数字,有两种情况:

    • i-1位与n都相同,这个时候我们只能填写[0|1, s[i]]之间的数字;
    • i-1位与n不相同,这个时候我们只能填写[0|1, 9]之间的数字;
  • 第9-10行是根据题意选择适合的计数方式。

2. 题解

class Solution:
    def atMostNGivenDigitSet(self, digits: List[str], n: int) -> int:
        s = str(n)

        @cache
        def f(i: int, mask: int, isLimit: bool, isNum: bool) -> int:
            if i >= len(s):
                return int(isNum)
            res = 0
            if not isNum:
                res += f(i+1, mask, False, False)
            up = int(s[i]) if isLimit else 9
            for digit in digits:
                num = int(digit)
                if isLimit and num > up:
                    break;
                res += f(i+1, mask, isLimit and digit == s[i] , True)
            return res

        return f(0, 0, True, False)