使用数字动态规划高效计算指定范围内数字和小于等于X的整数数量

本文详细介绍了如何利用数字动态规划(digit dp)算法,高效地统计在给定范围[0, n]内,其各位数字之和小于或等于特定值x的整数数量。针对n值高达10^12等大规模场景,该方法通过递归与记忆化结合,避免了暴力枚举的性能瓶颈,提供了清晰的算法原理、python实现示例及复杂度分析,并讨论了实际应用中的注意事项。

引言:问题背景与挑战

在处理大规模数据时,传统方法往往因其高时间复杂度而变得不可行。例如,当需要统计1到1012之间所有整数中,其各位数字之和小于或等于特定值X的整数数量时,简单地遍历每个数字并计算其数字和将导致严重的性能问题。对于N值高达1012的情况,这种暴力枚举的时间复杂度为O(N * logN),显然无法满足实际需求。为了克服这一挑战,我们需要一种更为高效的算法,数字动态规划(Digit DP)正是解决此类问题的理想选择。

数字动态规划(Digit DP)算法原理

数字动态规划是一种常用于解决“计算在某个区间[L, R]内满足特定条件的数字个数”问题的技术。这类问题通常可以通过计算count(R) - count(L-1)来解决,其中count(N)表示计算在区间[0, N]内满足条件的数字个数。

对于本问题,我们的目标是计算DS(limit_str, max_sum),即在[0, limit_str](limit_str是N的字符串表示)范围内,各位数字之和不超过max_sum的整数数量。

该算法的核心思想是:从最高位开始,逐位确定数字,并利用递归和记忆化(memoization)来避免重复计算。

状态定义:
我们的DP状态可以定义为 D(current_num_str, current_max_sum),表示在由 current_num_str 表示的数字范围内,各位数字之和不超过 current_max_sum 的数字个数。这里的 current_num_str 既可以是原始数字的后缀,也可以是全为 '9' 的字符串(表示当前位之前的选择已经放宽了限制)。

递归逻辑:

  1. 基准情况(Base Case):
    如果 current_num_str 只有一个数字(即我们正在处理个位),那么满足条件的数字就是从 0 到 min(current_max_sum, int(current_num_str[0]))。因此,结果为 min(current_max_sum, int(current_num_str[0])) + 1。

  2. 递归步骤:
    对于 current_num_str 的第一位 top_digit = int(current_num_str[0]),我们尝试放置 d 作为当前位的值。d 的取值范围是从 0 到 min(current_max_sum, top_digit)。
    对于每一个可能的 d:

    • 情况一:d < top_digit
      这意味着我们当前选择的数字 d 小于 current_num_str 的首位。这表明从当前位开始,后续的数字不再受 current_num_str 的限制(即“紧密”限制被打破)。因此,后续的 len(current_num_str) - 1 位可以是任意数字(0-9)。为了计算所有可能,我们将剩余的字符串视为一个由 len(current_num_str) - 1 个 '9' 组成的数字(例如,如果 current_num_str 是 "112",d 是 "0",那么我们正在计算形如 "0xx" 的数字,其中 xx 可以是 "00" 到 "99")。
      递归调用:D('9' * (len(current_num_str) - 1), current_max_sum - d, cache)。
    • 情况二:d == top_digit
      这意味着我们当前选择的数字 d 等于 current_num_str 的首位。这表明后续的数字仍然受到 current_num_str 的限制(即“紧密”限制仍然存在)。因此,我们直接取 current_num_str 的剩余部分 current_num_str[1:] 作为新的 current_num_str。
      递归调用:D(current_num_str[1:], current_max_sum - d, cache)。

    将所有 d 对应的递归结果累加,即可得到当前状态的结果。

    怪兽AI数字人

    数字人短视频创作,数字人直播,实时驱动数字人

    44

    查看详情

记忆化(Memoization):
为了避免重复计算相同的子问题,我们使用一个字典 cache 来存储 (current_num_str, current_max_sum) 状态的结果。在每次计算前,先检查 cache 中是否已存在结果;如果存在,则直接返回,否则计算并将结果存入 cache。

示例解析:DS(112, 5)

让我们通过计算 DS(112, 5)(即在 0 到 112 之间,数字和小于等于 5 的整数数量)来理解上述过程:

  1. DS(112, 5)

    • current_num_str = '112', current_max_sum = 5
    • top_digit = 1
    • d 的取值范围:0 到 min(5, 1),即 0, 1。
    • 当 d = 0 时 (d < top_digit):

      • 递归调用 D('99', 5 - 0, cache) -> D('99', 5, cache)
        • 这个调用会计算所有两位数(0-99)中数字和小于等于5的数字。
        • 其内部会进一步分解为 D('9', 5-0) + D('9', 5-1) + ... + D('9', 5-5),最终结果为 6+5+4+3+2+1 = 21。
    • 当 d = 1 时 (d == top_digit):

      • 递归调用 D('12', 5 - 1, cache) -> D('12', 4, cache)
        • current_num_str = '12', current_max_sum = 4
        • top_digit = 1
        • d 的取值范围:0 到 min(4, 1),即 0, 1。
        • 当 d = 0 时 (d < top_digit):

          • 递归调用 D('9', 4 - 0, cache) -> D('9', 4, cache)
            • 基准情况:min(4, 9) + 1 = 5。
        • 当 d = 1 时 (d == top_digit):

          • 递归调用 D('2', 4 - 1, cache) -> D('2', 3, cache)
            • 基准情况:min(3, 2) + 1 = 3。
        • D('12', 4, cache) 的结果是 5 + 3 = 8。
    • DS(112, 5) 的最终结果是 21 + 8 = 29。

Python 实现

以下是基于上述原理的 Python 实现:

def D(num_str: str, max_allowed_sum: int, cache: dict) -> int:
    """
    递归函数,计算在 num_str 表示的数字范围内,各位数字之和不超过 max_allowed_sum 的数字个数。

    Args:
        num_str (str): 当前处理的数字字符串,可以是原始数字的后缀,或全'9'字符串。
        max_allowed_sum (int): 允许的最大数字和。
        cache (dict): 用于存储已计算结果的记忆化字典。

    Returns:
        int: 满足条件的数字个数。
    """
    # 基准情况:如果只剩一个数字位
    if len(num_str) == 1:
        # 结果是 0 到 min(max_allowed_sum, int(num_str[0])) 之间的数字个数
        return min(max_allowed_sum, int(num_str[0])) + 1

    # 检查缓存,避免重复计算
    state = (num_str, max_allowed_sum)
    if state in cache:
        return cache[state]

    top_digit = int(num_str[0])
    # 构建一个由 (len(num_str) - 1) 个 '9' 组成的字符串,用于表示“紧密”限制被打破的情况
    nines_str = '9' * (len(num_str) - 1)

    current_count = 0
    # 遍历当前位可能的数字 d
    # d 的最大值不能超过 max_allowed_sum,也不能超过 num_str 的首位 (top_digit)
    for d in range(min(max_allowed_sum, top_digit) + 1):
        if d  int:
    """
    计算在范围 [0, n] 内,各位数字之和小于或等于 x 的整数数量。

    Args:
        n (int): 范围上限。
        x (int): 各位数字之和的上限。

    Returns:
        int: 满足条件的整数数量。
    """
    # 将 n 转换为字符串以便按位处理
    n_str = str(n)
    # 初始化缓存
    cache = {}
    return D(n_str, x, cache)

# 示例使用
n_example = 112
x_example = 5
result = count_digit_sums_le_x(n_example, x_example)
print(f"在 0 到 {n_example} 之间,数字和小于等于 {x_example} 的整数数量为: {result}")

# 验证对于大数值的效率
n_large = 10**12 - 1 # 例如 999...9 (12个9)
x_large = 50 # 举例,最大数字和为 9*12 = 108
result_large = count_digit_sums_le_x(n_large, x_large)
print(f"在 0 到 {n_large} 之间,数字和小于等于 {x_large} 的整数数量为: {result_large}")

时间复杂度分析

该数字动态规划算法的效率显著高于暴力枚举。

  • 状态数量:

    • num_str 的长度 L 大约为 log10(N)。对于 N=10^12,L 最大为 12。
    • max_allowed_sum 的最大值 X_max 大约为 9 * L。对于 L=12,X_max 最大为 9 * 12 = 108。
    • 因此,缓存中存储的状态数量大约是 L * X_max,即 12 * 108 左右,约为 1300 个状态。
  • 每个状态的计算:
    每个状态的计算涉及一个循环,迭代 d 从 0 到 `min(

以上就是使用数字动态规划高效计算指定范围内数字和小于等于X的整数数量的详细内容,更多请关注昆居客【www.kunjuke.com】。

相关推荐:

研究显示:同一款索尼游戏实体版或比数字版便宜 40-50 欧元

7 月 11 日消息,索尼近日宣布,将从 2028 年开始停止发行第一方游戏的实体光盘。据报道,索尼希望借此削减生产和发行成本,提高游戏销售利润,并尽可能增加收入。最新研究显示,同一款索尼游戏的实体版可能比 PlayStation 商店数字版便宜 40 至 50 欧元(注:现汇率约合 309.8 至 387.3 元人民币)。不少游戏登陆 PlayStation 商店已有多年,数字版却依然维持首发原...

农林废弃物变“黑金”:我国实现生物炭高效规模化制备,技术达国际领先水平

7 月 8 日消息,据科技日报,历经十余年持续攻关,我国在农林生物质资源化利用领域取得重要突破,实现了生物炭的大规模高效制备。7 月 5 日,由合肥综合性国家科学中心环境研究院牵头完成的“面向高值功能碳基材料创制的农林生物质高效转化关键技术与装备”科技成果评价会在安徽合肥举行。由两院院士及知名专家组成的评价委员会经过质询与讨论后认为,该项目成果总体达到国际先进水平,其中在生物质催化转化一步法制备生...

《红色沙漠》游戏高效捕鱼方法介绍

《红色沙漠》中的抓鱼钓鱼也是游戏里比较重要的一种收入来源,而想要快速捕鱼就需要一些技巧,其中速度最快的捕鱼方法就是用鱼笼,捕鱼点位的位置就在佩伦城下方区域的格雷洛克渡口码头。 红色沙漠怎么快速捕鱼 本文内容来源于互联网,如有侵权请联系删除。

Devin AI隐藏技巧:内置工具与快捷键高效使用【技巧】

Devin AI隐藏快捷键可大幅提升效率:Ctrl+Shift+D/Cmd+Shift+D直启主工作区;Ctrl+`秒切编辑器与Shell;/shell、/debug、/stop精准控制AI行为;Ctrl+Shift+H收起工具栏,/browse唤起浏览器。 ☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 多模态理解力帮你轻松跨越从0到1的创作门槛☜☜☜ 想在Devin AI中快速调用内置工...

Gemini3.5高效办公完整流程实战从文档到表格到PPT

做日常办公时,我习惯在kula ai聚合平台(leadhi.cn)上并行调用多个模型,横向对比输出质量,快速判断哪个模型在哪个环节更顺手、更高效。最近两周恰逢google发布gemini 3.5 flash,我趁机把它在文档撰写、表格分析、ppt制作等核心办公场景中完整跑了一遍。本文就结合真实使用体验,聊聊它带来的效率跃迁,以及那些真正经得起反复验证的实操技巧。 ☞☞☞AI 智能聊天, 问答助手,...
· AI · 143583

UC浏览器如何设置双击关闭标签页_UC浏览器高效操作技巧

UC浏览器双击关闭非当前标签页需通过五种方法实现:一、标准标签页设置启用双击关闭;二、实验室功能开启高级双击选项;三、快捷键替代(Ctrl/Cmd+W等);四、Android端用Auto.js等工具模拟双击;五、关闭后台自动清理机制防误关。 如果您希望在UC浏览器中通过双击操作快速关闭非当前标签页,但无法在设置中直接找到对应选项,则可能是由于版本差异、平台限制(Android/iOS/Window...

视频剪辑移动固态硬盘推荐:三星 T9 高效解决素材难题

视频剪辑新装备:三星 T9 移动固态硬盘 当前视频制作行业,素材整理与调用已成为内容创作者面临的一大挑战。一场中等规模的4K/8K拍摄,所产生的原始素材往往轻松突破数百GB,甚至逼近TB级。若依赖传统存储设备进行传输,速度瓶颈明显;而云方案虽具灵活性,却受限于网络带宽与上传下载周期,难以支撑即时剪辑、现场粗剪等高频操作需求。此时,三星 T9 移动固态硬盘应运而生,以高性能表现直击行业痛点。 为何视...

谷歌浏览器怎么管理标签页_谷歌浏览器管理标签页技巧【高效】

Chrome提供五种原生标签页管理方法:一、右键单标签“添加到新组”并命名配色;二、Ctrl/Cmd多选后右键“将标签页分组”批量归类;三、拖拽或右键将新页加入现有分组;四、启用垂直标签页布局释放横向空间;五、固定常用标签页防误关。 如果您在谷歌浏览器中同时打开大量标签页,导致标签栏拥挤、切换困难、视觉混乱,可借助 Chrome 原生提供的多种结构化管理功能进行高效整理。以下是解决此问题的步骤: ...