1. 数位DP算法概述
数位DP(Digit Dynamic Programming)是动态规划在数字处理问题中的一种特殊应用。这类算法主要用于解决与数字各位数相关的计数问题,比如统计某个区间内满足特定条件的数字个数。我第一次接触这个算法是在解决LeetCode上一道统计不含连续1的数字数量时,发现暴力枚举完全不可行,这才意识到需要更高效的解法。
数位DP的核心思想是将数字看作由各位组成的序列,通过记忆化搜索的方式避免重复计算。与常规动态规划不同,数位DP需要考虑数字本身的位数限制以及前导零等特殊情况。在实际编程竞赛和面试中,约15%的数字处理问题都可以通过数位DP高效解决。
关键提示:数位DP特别适合处理区间统计问题,比如计算[L, R]范围内满足某种数字特征的数量。直接遍历区间的方法在R很大时(如1e18)完全不可行,而数位DP可以将时间复杂度降至O(logR)。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数位DP三大经典题型解析
2.1 题型一:数字限制类问题
这类问题要求统计满足特定数字组成条件的数字数量。典型例题包括:
- 不含连续1的数字(LeetCode 600)
- 不含某些禁用数字的数字
- 包含特定数字模式(如"13")的数字
解法模板:
- 将数字转换为字符串或数组处理
- 设计状态表示(通常包含:当前位数、前导零状态、是否已达上限、额外约束条件)
- 实现记忆化DFS函数
python复制def countSpecialNumbers(n: int) -> int:
s = str(n)
@lru_cache(maxsize=None)
def dfs(pos, tight, lead_zero, prev_digit, memo):
if pos == len(s):
return 0 if lead_zero else 1
limit = int(s[pos]) if tight else 9
res = 0
for d in range(0, limit + 1):
new_tight = tight and (d == limit)
new_lead = lead_zero and (d == 0)
# 根据题目条件添加约束判断
if not new_lead and abs(d - prev_digit) == 1:
continue
res += dfs(pos + 1, new_tight, new_lead, d, memo)
return res
return dfs(0, True, True, -2, {})
实战技巧:
- 使用
tight标记当前是否仍受原数字限制 lead_zero处理前导零特殊情况- 根据题目要求修改约束条件判断部分
2.2 题型二:数字和类问题
这类问题关注数字各位数之间的关系,如:
- 数位和为特定值的数字个数
- 数位乘积满足条件的数字
- 数位满足某种数学关系的数字
状态设计要点:
- 额外维护当前和/积作为状态参数
- 可能需要记录前几位数字的值
- 注意乘积为0的特殊情况处理
python复制def digitSumDP(low, high, target):
def f(s):
n = len(s)
@lru_cache(None)
def dfs(pos, tight, sum_left):
if pos == n:
return 1 if sum_left == 0 else 0
limit = int(s[pos]) if tight else 9
res = 0
for d in range(0, limit + 1):
if d > sum_left:
continue
new_tight = tight and (d == limit)
res += dfs(pos + 1, new_tight, sum_left - d)
return res
return dfs(0, True, target)
return f(str(high)) - f(str(low - 1))
优化技巧:
- 对于和类问题,可以提前终止不可能路径(如当前和已超过目标)
- 乘积类问题可对中间结果取模避免溢出
- 使用哈希表代替数组存储状态节省空间
2.3 题型三:数字模式类问题
这类问题涉及数字的排列组合特征:
- 回文数字计数
- 数字满足某种波浪形(增减交替)
- 数字包含特定子序列
解决方案特点:
- 需要记录前几位数字的模式
- 可能涉及复杂的状态转移条件
- 通常需要更高维度的状态表示
python复制def countPalindromicNumbers(low, high):
def is_palindrome(x):
s = str(x)
return s == s[::-1]
def dp(s):
n = len(s)
@lru_cache(None)
def dfs(pos, tight, left_part):
if p
