题目描述
思路描述
清华出版社出版的那本《算法竞赛》(白皮的,分上下册,作者罗勇军+郭卫斌)在对数位DP的描述的中,我觉得还是太生涩了,这让人感觉数位DP好像需要很大的思考量。 但其实数位DP只需板子就能解决。
这道题目需要求出[1,n]区间内,特殊整数的个数。 特殊整数的定义是,整数的每个数位互不相同。
我们的板子代码直接从模拟填充的思路入手,将所有合法的填充情况统统相加,代码量小而且思考量低。
-
将右区间
n视为一个字符串s,有len(s)个数位。我们模拟从左向右进行填充(或者说从高位向低位进行填充)。 -
如果填充数位的下标已经达到了
len(s),说明已经完全填充完毕。但判断当前填充完毕的情况是否为合法的条件,在于填充的所有位是不是全都是前导零?如果是,那么is_num从顶到底一路都是False,最终填充完毕的情况也是一个非法情况,直接返回0;否则是某个合法的情况,返回1. -
如果当前
is_num为False,说明这一路都是前导零,你可以选择跳过当前位不填充,继续深搜。 -
决定当前的上限
up是多少?如果当前is_limit是True,说明从上一个情况传到当前情况的这一路下来,填充数位都顶到了右边界s的相应数位的最高数值,自然当前up就应该继续是右边界s的当前数位的最高数值int(s[idx]) -
那么当前枚举的下限是多少呢?如果前面一直是前导零,那么我当前想要填充数字,必须从1开始填;如果前面不是前导零,说明我当前可以从0开始填。因此有
1-int(is_num) -
mask表示这一路以来已经填充了哪些数字,因为题目要求填充的数字各不相同。这个mask当一个set来使用。
-
额外解释1:为什么12行第三个参数是False。因为你一路以来都是前导零,已经对后面的填充没有了约束。你想想,这个约束因何存在?只有从最高数位开始非前导零且一直顶着右边界对应高位进行填充,这个约束才一直会生效。 否则不生效,你可以随便填。
-
额外解释2:16行的
is_limit and d == up是个什么意思?还是那句话,只有从最高数位开始非前导零且一直顶着右边界对应高位进行填充,这个约束才一直会生效, 我们就靠着这个将约束传递下去。只有这一路之前是一直被约束着的,而且当前再次顶到了右边界对应数位最高值,那么这个约束再次成立,将继续传递。除此之外,约束都不会生效。 -
额外解释3:为什么答案是
dfs(0,0,True,False)? 第一个0表示从第0下标开始填充(对应右边界最高数位,对应右边界字符串第0下标位)。第二个0表示当前谁也没有进行选择,所以是空的集合。第三个参数为True表示当前最高位需要进行限制,你如果为False那上限就是9了,显然不对,上限应该为int(s[0])。最后一个参数为False表示当前还不构成数字。
代码
class Solution:
def countSpecialNumbers(self, n: int) -> int:
s = str(n)
@cache
def dfs(idx : int , mask : int , is_limit : bool , is_num : bool) -> int :
if idx == len(s) :
return int(is_num)
res = 0
if not is_num :
res = dfs(idx + 1 , mask , False , False)
up = int(s[idx]) if is_limit else 9
for d in range(1 - int(is_num) , up + 1) :
if mask >> d & 1 == 0 :
res += dfs(idx + 1 , mask | (1 << d) , is_limit and d == up , True)
return res
return dfs(0 , 0 , True , False)