数位DP我一直很头疼,直到用了灵神的记忆化模板。
用记忆化搜索的话,复杂度比数组形式dp高一些,但是胜在思路清晰,套路固定。
由于复杂度取决于状态数,在int下,位数最多就是10,所以重头应该在mask,一般都能过。
def f(i,mask,is_limit,is_num):
class Solution:
def countNumbersWithUniqueDigits(self, n: int) -> int:
if n == 0:
return 1
s = str(10**n-1)
n = len(s)
@cache
def f(i,mask,is_limit,is_num):
if i == n:
return int(is_num)
up = int(s[i]) if is_limit else 9
down = 0 if is_num else 1
ans = 0
if not is_num:
ans += f(i+1,0,False,False)
for j in range(down,up+1):
if mask & (1<<j) == 0:
ans += f(i+1,mask|(1<<j),is_limit and j == up,True)
return ans
return f(0,0,True,False)+1
由于至少有一位代表可以有多位,不好分析,于是直接转化成没有重复,变成上一道题。
class Solution:
def numDupDigitsAtMostN(self, n: int) -> int:
x = n
s = str(n)
n = len(s)
@cache
def f(i,mask,is_limit,is_num):
if i == n:
return int(is_num)
up = int(s[i]) if is_limit else 9
down = 0 if is_num else 1
ans = 0
if not is_num:
ans += f(i+1,0,False,False)
for j in range(down,up+1):
if mask & (1<<j) == 0:
ans += f(i+1,mask|(1<<j),is_limit and j == up,True)
return ans
# print(x,f(0,0,True,False))
return x- f(0,0,True,False)
链接: 1742. 盒子中小球的最大数量
参见: [LeetCode解题报告] 1742. 盒子中小球的最大数量](https://blog.csdn.net/liuliangcan/article/details/127994903)
class Solution:
def countBalls(self, lowLimit: int, highLimit: int) -> int:
s1 = str(highLimit)
s2 = str(lowLimit)
n = len(s1)
if len(s2)< n: # 前边补齐0
s2 = '0'*(n-len(s2)) + s2
@cache
def f(i,s,is_up_limit,is_down_limit,is_num): # 枚举到第几位,从i往后的位和,上限是否受到前一位限制,下限是否受到前一位限制,前边是否填过数;返回这个s下的方案数
if s < 0:
return 0
if i == n:
return int(is_num and s == 0)
up = int(s1[i]) if is_up_limit else 9
down = int(s2[i]) if not is_num or is_down_limit else 0 # 前一位没填,或者下限受限,down都=s2
ans = 0
for j in range(down,up+1):
ans += f(i+1,s-j,j == up and is_up_limit,is_down_limit and j == down,is_num or j>0 )
return ans
# for i in range(46):
# print(i,f(0,i,True,False))
# print(f(0,6,True,True,False))
return max(f(0,i,True,True,False) for i in range(46))
链接: 233. 数字 1 的个数
链接: [面试题 17.06. 2出现的次数](面试题 17.06. 2出现的次数)
class Solution:
def countDigitOne(self, x: int) -> int:
s = str(x)
n = len(s)
@cache
def f(i,cnt1,is_limit,is_num):
if i == n:
return cnt1
ans = 0
# if not is_num:
# ans += f(i+1,cnt1,False,False)
r = int(s[i]) if is_limit else 9
# l = 0 if is_num else 1
l = 0
for j in range(l,r+1):
ans += f(i+1,cnt1+(j==1),is_limit and j == r,True)
return ans
return f(0,0,True,False)
链接: 600. 不含连续1的非负整数
class Solution:
def countDigitOne(self, x: int) -> int:
s = str(x)
n = len(s)
@cache
def f(i,cnt1,is_limit,is_num):
if i == n:
return cnt1
ans = 0
# if not is_num:
# ans += f(i+1,cnt1,False,False)
r = int(s[i]) if is_limit else 9
# l = 0 if is_num else 1
l = 0
for j in range(l,r+1):
ans += f(i+1,cnt1+(j==1),is_limit and j == r,True)
return ans
return f(0,0,True,False)
链接: 902. 最大为 N 的数字组合
class Solution:
def atMostNGivenDigitSet(self, digits: List[str], x: int) -> int:
s = str(x)
n = len(s)
@cache
def f(i,is_limit,is_num):
if i == len(s):
return int(is_num)
ans = 0
if not is_num:
ans += f(i+1,False,False)
up = s[i] if is_limit else '9'
for j in digits:
if j <= up:
ans += f(i+1,is_limit and j == up,True)
return ans
return f(0,True,False)