【算法】力扣【动态规划、数位DP模板题】233. 数字 1 的个数

233. 数字 1 的个数


【算法】力扣【动态规划、数位DP】233. 数字 1 的个数

题目描述

本文旨在解析力扣算法题233:“数字 1 的个数”。难度等级:困难。该算法问题要求计算在非负整数n以内(包括n),所有数位上数字1出现的次数。这是一道数位DP模板题。

这里的解法参考了灵神(灵茶山艾府)的第二版数位DP

输入输出示例

示例 1:

输入:n = 13
输出:6

解释: 数字1在以下数字中出现:1, 10, 11, 12, 13,其中11中数字1出现两次,合计6次。

示例 2:

输入:n = 0
输出:0

解释: 没有数字1出现。

提示

  • 0 ≤ n ≤ 1 0 9 0 \leq n \leq 10^9 0n109

解题思路

解决这个问题的关键是使用数位动态规划(数位DP)。数位DP是一种处理数位统计问题的有效方式。基本思想是将数字从高位到低位逐位考虑,利用历史信息减少计算量。其主要步骤包括定义状态、状态转移以及记忆化搜索。

代码解析

以下是题解的代码部分,我们将按函数的逐行解释:

第一部分
class Solution:
    def countDigitOne(self, n: int) -> int:

        # 将输入数字转换为字符串,方便逐位处理
        high = str(n)
        n = len(high)
        low = '0' * n

在这段代码中,我们固定了数字的长度为n,并将其低位填充为0,创建了一个同等长度的字符串low,用于之后与high进行比较。

第二部分
        @cache
        def dfs(i: int, limit_low: bool, limit_high: bool, last_one):
            # 当递归处理完所有位时返回统计的1的数量
            if i == n:
                return last_one

这个dfs函数是深度优先搜索的主体,带有Python的@cache装饰器来缓存中间结果,避免重复计算。参数i代表当前处理的位,limit_lowlimit_high分别表示是否受到最低位和最高位的限制,last_one是迄今为止统计的1的个数。

第三部分
            res = 0
            
            # 根据限制确定当前位的遍历范围
            lo = int(low[i]) if limit_low else 0
            hi = int(high[i]) if limit_high else 9
            
            # 遍历当前位可能的数字,并递归到下一位
            for nx in range(lo, hi + 1):
                # 累计1的个数,并递归处理下一位
                res += dfs(i + 1, limit_low and lo == nx, limit_high and hi == nx, last_one + int(nx == 1))
            return res

        return dfs(0, True, True, 0)

在这段代码中进行了遍历处理,从lohi代表当前位可能的取值范围。如果当前位是1,则last_one增加,反之保持不变。递归调用dfs处理下一位,并累加结果。

完整Python3代码

class Solution:
    def countDigitOne(self, n: int) -> int:
    
        high = str(n)
        n = len(high)
        low = '0' * n

        @cache
        def dfs(i: int, limit_low: bool, limit_high: bool, last_one):
            if i == n:
                return last_one

            res = 0
            
            lo = int(low[i]) if limit_low else 0
            hi = int(high[i]) if limit_high else 9
            
            for nx in range(lo, hi + 1):
                res += dfs(i + 1, limit_low and lo == nx, limit_high and hi == nx, last_one + int(nx == 1))

            return res

        return dfs(0, True, True, 0)

复杂度分析

  • 时间复杂度: O ( log ⁡ n ) O(\log n) O(logn),其中 l o g log log代表数字n的位数。我们是按位处理,利用@cache避免了重复计算子问题,每一位的循环次数不超过10次(数位只能是 0 ~ 9),总体是对数级别的复杂度。
  • 空间复杂度: O ( log ⁡ n ) O(\log n) O(logn),用于缓存的空间大小和数字位数成正比。

总结

本文详细解析了力扣233题“数字 1 的个数”,使用数位DP不仅可以高效解决问题,同时也能够提升算法设计的灵活性和应用范围。在处理大量数据且关注数位特征的算法问题时,数位DP无疑是一个强大的工具。通过递归和记忆化搜索的手段,我们能够避免重复计算,从而大幅降低时间复杂度。

  • 26
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值