数位 DP 一般用于求解区间 [L, R] 中满足特定条件的数,由于区间范围过大,直接枚举会超时,借助数位 DP 思想可设计出 log 级别的复杂度算法。数位指个位、十位、百位、千位等,数位DP就是在数位上进行动态规划。数位DP在实质上是一种有策略的穷举方式,在子问题求解完毕后将其结果记忆化就可以了。
数位DP 核心思想是逐位枚举,确定答案,即将一个整数的各个位拆出来确定其数值范围,进而计算出整个区间中满足条件的数。
如何枚举?
比如枚举[0,386]区间的所有数时,首先从百位开始枚举,百位可能是0、1、2、3。枚举时不超过386即可。
⚪百位0:十位可以是0~9,个位也可以是0~9,枚举没有限制,因为百位是0时,后面的位数无论是多少,都不可能超过386,相当于枚举000~099。
⚪百位1:十位可以是0~9,个位也可以是0~9,枚举没有限制,枚举100~199。
⚪百位2:十位可以是0~9,个位也可以是0~9,枚举没有限制,枚举200~299。
⚪百位3:十位只可以是0~8,否则超过386,此时是有上界限制的。当十位是0~7时,个位可以是0~9,因为379还是不会超过386。但当十位是8时,个位只可以是0~6,此时有上界限制,相当于枚举300~379、380~386。
数位DP需要注意的几个问题:
(1)记忆化。无限制时,可以记忆化;有限制时,不可以记忆化,需要继续根据限制枚举。枚举[0,386]区间的所有数,当百位是0~2时,十位和个位枚举没有限制,都是一样的,采用记忆化递归,只需计算一次并将结果存储起来,下次判断若已赋值,则直接返回该值即可。百位是3时,十位限制在0~8;十位是0~7时,个位无限制;十位是8时,个位限制在0~6。
(2)上界限制。当高位枚举刚好达到上界时,紧接着的下一位枚举就有上界限制了。可以设置一个变量limit标记是否有上界限制。
(3)高位枚举0。为什么高位需要枚举0?这是因为百位枚举0相当于此时枚举的这个数最多是两位数,若十位继续枚举0,则枚举的是一位数。枚举小于或等于386的数,一位数、两位数当然也比它小,所以高位要枚举0。
(4)前导0。有时会有前导0的问题,可以设置一个lead变量表示有前导0。例如统计数字里面0出现的次数。若有前导0,例如008,数字8不包含0,则不应该统计8前面的两个0。若没有前导0,例如108,则应该统计8前面的1个0。