【算法】力扣【动态规划、数位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 0≤n≤109
解题思路
解决这个问题的关键是使用数位动态规划(数位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_low
和limit_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)
在这段代码中进行了遍历处理,从lo
到hi
代表当前位可能的取值范围。如果当前位是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无疑是一个强大的工具。通过递归和记忆化搜索的手段,我们能够避免重复计算,从而大幅降低时间复杂度。