数位 DP:按数字位统计范围内的合法数量

讲清 pos、tight、started 和业务状态,使用记忆化搜索统计范围内满足条件的数字数量。

已发布文章计算机基础入门4 分钟阅读发布于 2026年9月1日更新于 2026年9月15日
文章目录
  1. 数位 DP 解决什么问题
  2. 核心状态
  3. 例子:统计不含数字 4 的数量
  4. 为什么 tight 不能随便缓存
  5. 处理区间 [L, R]
  6. 我的分析
  7. 面试题
知识目录数据结构与算法:从基础到工程实践34 / 77
LOCAL NOTE高亮并记笔记
0 / 1000 · 只保存在这台设备
READER NOTES数位 DP:按数字位统计范围内的合法数量》的本机笔记

“数位 DP:按数字位统计范围内的合法数量”这一块,我曾经有一种很典型的错觉:内容看懂了,自己也会了。我真正关掉答案后才发现:数位 DP 是我看题解也容易迷路的一类题,尤其是前导零、上界限制和记忆化状态,经常少一个条件就算错。后来我用画图、手算和最小代码反复验证,才把表面的熟悉变成可以复述的理解。

数位 DP 用来统计某个范围内满足条件的数字数量,比如 1..n 中不含某个数字的数、有多少个数字包含重复位、数字之和满足条件的个数。

数位 DP 解决什么问题

普通枚举 1..nn 很大时不可行。数位 DP 不枚举每个数字,而是从高位到低位决定每一位可以放什么。

数位 DP tight 状态

图:tight 表示当前前缀是否仍然贴着上界

核心状态

数位 DP 常见参数:

  • pos:当前处理到第几位。
  • tight:前面位是否都贴着上界。
  • started:是否已经开始放非前导零数字。
  • 业务状态:比如数字和、是否出现某个数字、使用过的数字集合。

例子:统计不含数字 4 的数量

class DigitDP {
    char[] digits;
    Integer[][] memo;

    int countWithoutFour(int n) {
        digits = String.valueOf(n).toCharArray();
        memo = new Integer[digits.length][2];
        return dfs(0, true, false);
    }

    int dfs(int pos, boolean tight, boolean started) {
        if (pos == digits.length) {
            return started ? 1 : 0;
        }
        if (!tight && memo[pos][started ? 1 : 0] != null) {
            return memo[pos][started ? 1 : 0];
        }
        int limit = tight ? digits[pos] - '0' : 9;
        int ans = 0;
        for (int d = 0; d <= limit; d++) {
            if (d == 4) continue;
            boolean nextStarted = started || d != 0;
            boolean nextTight = tight && d == limit;
            ans += dfs(pos + 1, nextTight, nextStarted);
        }
        if (!tight) memo[pos][started ? 1 : 0] = ans;
        return ans;
    }
}

真实写法里,memo 的维度要包含所有会影响未来决策的状态。

为什么 tight 不能随便缓存

如果 tight=true,当前位置能选的最大数字受上界限制。不同上界前缀下,结果不同,所以通常只缓存 tight=false 的状态。

处理区间 [L, R]

数位 DP 常用技巧是把区间问题变成前缀问题:

answer(L, R) = count(0, R) - count(0, L - 1)

这样只需要写一个 count(n)

我的分析

数位 DP 看起来抽象,但其实就是“按位构造数字”。它适合范围巨大但位数有限的问题,比如 10^18 也只有 19 位。关键是状态维度不能漏:如果题目限制数字不能重复,就需要 mask;如果限制数字和,就要记录 sum

面试题

  1. 数位 DP 中 tight 表示什么?
  2. 为什么通常只缓存 tight=false 的状态?
  3. 前导零为什么需要单独处理?
  4. 区间 [L, R] 为什么可以转成两个前缀计数?
  5. 数位 DP 适合什么规模的问题?
JARVIS · 当前文章

有哪里没看懂?可以只问这篇。

Jarvis 会限定在《数位 DP:按数字位统计范围内的合法数量》及其公开关联内容中检索,并把引用定位回原文章节。

JARVIS / ARTICLE针对《数位 DP:按数字位统计范围内的合法数量》提问
当前范围数位 DP:按数字位统计范围内的合法数量不会悄悄扩大到全站
0 / 1000

准备好了。当前只会围绕数位 DP:按数字位统计范围内的合法数量回答。

READER SIGNAL

这篇内容对你有帮助吗?

不需要登录。你的反馈会直接进入作者待处理列表。