You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求[0,k]内数位和为s的整数个数,寻求更优解法(k极大)

计算[0,k]内数位和等于s的整数个数(大数优化)

问题描述

需要计算范围**[0,k]**内数位和等于s的整数个数,k可能是极大数,因此不能用O(k)的暴力解法。我尝试了O(s log(k))复杂度的思路(log(k)对应k的位数),引入了cnt_sum函数计算n位数(含前导零)中数位和等于s的个数,但遇到了前导零导致的重复统计问题,有没有更简洁的解决办法?

附上我写的未完善伪代码:

# 伪代码,未实现记忆化和边界情况处理

# 计算n位数中数位和等于s的整数个数
# 包含前导零
def cnt_sum(n:int,s:int):
    ans=0
    for i in range(0,9):
        ans+=cnt_sum(n-1,s-i)
    return 0

# 假设数字为63069
def dp(loc:int, k:int, s:int):
    ans=0
    # 统计以6开头且剩余数位小于k[1:](3069)、数位和等于sum-k[0](sum-6)的数字
    ans+=dp(loc+1,k,s-k[loc])
    # 遍历[0,5]中的每个数字i,统计所有长度为len(k)-loc且数位和等于sum-i的数字
    # 例如59998、49999
    for i in range(0,k[loc]):
        ans+=cnt_sum(len(k)-loc,s-i)
    return ans

def count(k:int,s:int):
    dp(0,k,s)

最优解法:带约束的数位DP

你的思路方向是对的,但cnt_sum的实现有问题,且没有处理前导零和边界条件。更简洁可靠的方式是直接用数位DP,通过记忆化搜索处理约束(不超过k的数位),同时自然避免前导零重复问题。

核心思路

数位DP的本质是逐位枚举数字,记录四个关键状态:

  1. 当前处理到第几位(pos)
  2. 已选数位的和(current_sum)
  3. 是否已经小于k的前缀(tight,布尔值:True表示当前前缀等于k的前缀,后续数位不能超过对应位;False表示前缀已经更小,后续数位可以0-9随意选)
  4. 是否是前导零(leading_zero,布尔值:True表示前面全是0,当前位可以继续选0,此时不计入数位和)

通过记忆化缓存这四个状态的结果,避免重复计算,时间复杂度为O(位数 × s × 2 × 2),完全满足大数场景。

完整实现代码(Python)

def count_numbers_with_digit_sum(k: str, s: int) -> int:
    digits = list(map(int, k))
    n = len(digits)
    
    from functools import lru_cache
    
    @lru_cache(maxsize=None)
    def dp(pos: int, current_sum: int, tight: bool, leading_zero: bool) -> int:
        # 递归终止:处理完所有数位
        if pos == n:
            # 如果是前导零(即数字0),只有当s=0时算1个,否则0;否则判断当前和是否等于s
            return 1 if (leading_zero and s == 0) or (not leading_zero and current_sum == s) else 0
        
        limit = digits[pos] if tight else 9
        total = 0
        
        for d in range(0, limit + 1):
            new_tight = tight and (d == limit)
            new_leading_zero = leading_zero and (d == 0)
            new_sum = current_sum + (d if not new_leading_zero else 0)
            
            # 剪枝:如果当前和已经超过s,没必要继续
            if new_sum > s:
                continue
            
            total += dp(pos + 1, new_sum, new_tight, new_leading_zero)
        
        return total
    
    return dp(0, 0, True, True)

# 示例:计算[0,63069]中数位和等于15的个数
print(count_numbers_with_digit_sum("63069", 15))

关键细节说明

  1. 前导零处理:通过leading_zero状态标记,前导零阶段的0不会被计入数位和,避免了把"0012"和"12"当成不同数字重复统计的问题。
  2. 约束控制:tight状态确保我们只统计不超过k的数字,当tight为True时,当前位的最大值是k对应位的数字,否则可以选0-9。
  3. 记忆化优化:用lru_cache缓存状态结果,避免重复计算相同状态的子问题,大幅提升效率。
  4. 剪枝操作:当当前数位和已经超过s时,直接跳过后续递归,减少不必要的计算。

替代方案:修正你的cnt_sum函数

如果坚持用你最初的思路,需要先修正cnt_sum,并区分"n位数(不含前导零)"和"n位长度的数字(含前导零)":

  • 含前导零的n位数字数位和为s的个数:这是典型的整数拆分问题,可用动态规划计算,状态dp[i][j]表示i位数字(含前导零)数位和为j的个数,转移方程为dp[i][j] = sum(dp[i-1][j-d] for d in 0..9 if j-d >=0)
  • 不含前导零的n位数(即10(n-1)到10n-1之间的数)数位和为s的个数:等于cnt_sum(n, s) - cnt_sum(n-1, s)(减去前导零的情况)

但这种方式需要分别计算不同长度的数字,再加上数位长度小于k的情况,不如直接用数位DP简洁。

内容的提问来源于stack exchange,提问作者eigenless

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 00:20:04