偷师acwing,学习数位DP的策略。
引言
数位dp的特点,求解在一个区间内满足某特性的数字的个数。数位dp具有两个特点,第一个,利用前缀和的特点求解区间[left, right]
的问题,分别求出【0,left-1】和【0,right】的个数。第二个特点是树,对每一位依次进行分析。
核心的思想,首先我们需要维护一个dp数组,这个数组一般由这么几个状态组成:
剩余的位数i
,前一位的数组的填充j
,求解满足调节的必要信息k
。
具体的dp转移思路时,是与题目要求的条件有关。如要求出现几个1,全部数字不降,前后数字之差等。
在完成计算dp之后,需要开始求解在区间中满足条件的数字。首先把判断0的情况,然后数字拆分为每一位。计算每一位的情况时分为两种情况,比如这一位是an
,情况一是可以完整的取0~an-1
的全部情况,另外一种是这一位取an
,因为是无法完成取到全部的值的,注意这个情况要每次判断是否合法,不合法就break。
如果有幸能到最后一位数字,最后加一,表示最后一种情况。
例题 不降数字
给定区间[l, r]
求区间内所有是整数,满足低位不小于高位的数值的个数。如,1234,2333.
首先我们定义一个dp[i][j]
表示在还有i
位的情况下,最高位位j
的情况下满足的个数。
显然我们可以知道dp[i][j] += dp[i-1][k]
其中j<=k<10
.
然后我们分析问题,关键的信息有last
和res
,存储了上一位的信息,和当前的个数
第一步:把num拆分开成每一位的形式。从最高位开始,寻找可能的解。
第二步:结合树形图,依次求解。
class Solution():
def test(self, l, r):
# l表示左端点 0<=l<r:
# dp[i][j]表示存在i位,最大数字是j,满足非降的个数
# 设置为15保证了最长可以有15位的整数
dp = [[0]*10 for _ in range(15)]
for j in range(10):
dp[1][j] = 1
for i in range(2,15):
for j in range(10):
for k in range(j, 10):
dp[i][j] += dp[i-1][k]
#print(dp)
def helper(n):
res = 0
last = 0
num = []
# 首先判断0
if not n:
return 1
# 拆分数字
while n:
num.append(n%10)
n //= 10
for i in range(len(num)-1, -1, -1):
x = num[i]
# 判断有没有必要枚举
if last>x:
break
# 这种情况下没必要在枚举了,已经全部枚举了如12854,枚举完12700d的全部,剩下的不可能满足了
# 计算所有左侧的情况
for j in range(last, x):
res += dp[i+1][j]
# 右侧的情况
last = x