持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第19天,点击查看活动详情
1. 题目与解析
如果一个正整数每一个数位都是 互不相同 的,我们称它是 特殊整数 。
给你一个 正 整数
n,请你返回区间 **[1, n]之间特殊整数的数目。
输入: n = 20
输出: 19
解释: 1 到 20 之间所有整数除了 11 以外都是特殊整数。所以总共有 19 个特殊整数。
输入: n = 5
输出: 5
解释: 1 到 5 所有整数都是特殊整数。
输入: n = 135
输出: 110
解释: 从 1 到 135 总共有 110 个整数是特殊整数。
不特殊的部分数字为:22 ,114 和 131 。
这道题是数位DP的典型题目。
何为数位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 countSpecialNumbers(self, 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 num in range(1-int(isNum), up+1):
if (mask>>num) & 1 == 0:
res += f(i+1, 1<<num|mask, isLimit and num == up, True)
return res
return f(0, 0, True, False)