一、 数位DP
题目为:求[L, R]范围中有多少个满足条件的数字:先求[1,R]区间满足条件的个数,再求[0,L]区间,然后相减,这样做可以不考虑下界的限制条件。
限制条件:数字的每一位之间具有某种性质
此时可以使用数位DP
二、函数概念
处理上述问题时,一般我们使用枚举的方法,循环测试给定区间内的所有数字,并判断当前数字是否满足约束条件。在判定时通常也要使用循环,因此造成时间复杂度升高。
数位DP使用空间换取时间,使用递归,直接从每一数位开始构造数字,构造的同时判断是否满足条件,相对来说时间复杂度低。
常用参数:
1. pos: 当前执行的数位,从第1位开始而不是第0位
2. pre: pos的上一位的值,用于检测限制条件 注:pos是数位,而pre是数值
3. limit: 限制条件,决定pos可取的数字范围
4. 根据题目要求,适当添加额外的参数
DFS函数体模板(python):
def dfs(pos: int, pre: int, limit: bool) -> int:
if pos == len(#): #:给定的范围R
return 1 # 当执行到第0位时证明所有可能性都已构造完成,返回1或判断参数(数字最小位是1)
posmax = upperbound[pos] if limit else 9 # 确定当前数位可取的最大值
count = 0
for i in range(posmax + 1):
# 在此处进行判断,根据题目要求决定
count += dfs(pos + 1, i, limit and i == posmax)
return count
加入DP 注:如果只运行一次函数,在某些情况中不会有重复的pos和pre对出现,导致DP可能并没有提升的效果
def dfs(pos: int, pre: int , limit: bool) -> int:
if pos == len(#):
return 1
if not limit and dp[pos][pre] != None: return dp[pos][pre] # 如果算过相同的值,直接返回
posmax = upperbound[pos] if limit else 9 # 确定当前数位可取的最大值
count = 0
for i in range(posmax + 1):
# 在此处进行判断,根据题目要求决定
count += dfs(pos + 1, i, limit and i == posmax)
if not limit:
dp[pos][pre] = count # 如果limit == false,则递归中limit参数永远为false,因此可以记录下来反复使用结果
return count
函数详解:
使用dfs进行遍历,从数字的最高位开始构造:最高位受到限制,limit取True。此时可取的值为[0, posmax],使用递归的方法对下一位进行处理。如果本位的值低于posmax,则下一位不受限制,同时如果本位本就不受限制,则下一位一定不受限制(限制 = 123,num = 12x,则x可以取0123;num = 11x,则x可以取0-9;num = 09x,x一样可以取0-9)。所以在调用递归函数时,下一位的limit参数应该是limit and i == posmax,同时对上一位是否等于极限和本位是否等于极限进行判断。
在递归的最后,当pos = len时也就说明该数字构造完成,返回1,根据特殊题目可能返回一个参数表示该数是否满足条件。count即表示这些return的和。
LeetCode题目:
我们称一个数 X 为好数, 如果它的每位数字逐个地被旋转 180 度后,我们仍可以得到一个有效的,且和 X 不同的数。要求每位数字都要被旋转。
如果一个数的每位数字被旋转以后仍然还是一个数字, 则这个数是有效的。0, 1, 和 8 被旋转后仍然是它们自己;2 和 5 可以互相旋转成对方(在这种情况下,它们以不同的方向旋转,换句话说,2 和 5 互为镜像);6 和 9 同理,除了这些以外其他的数字旋转以后都不再是有效的数字。
现在我们有一个正整数 N, 计算从 1 到 N 中有多少个数 X 是好数?
def rotatedDigits(self, n: int) -> int:
upperbound = [int(x) for x in str(n)]
def dfs(pos: int, limit: bool , has: bool) -> int:
# has表示构造得到的数字是否含有2569
if pos == len(str(n)):
return has
posmax = upperbound[pos] if limit else 9
count = 0
for i in range(posmax + 1):
if i in [0,1,2,5,6,8,9]:
count += dfs(pos + 1, limit and i == posmax, has or i in [2,5,6,9])
# has参数记载本位以及所有上位中是否存在过2569
return count
return dfs(0, True, 0)
本题不需要对上位数值考虑,所以不包含pre参数,但包含has参数,用于记录是否出现过必须存在的值2569,如果存在过,则该数字满足条件,存放在count中。