SWE · Coding

TikTok 数位计数:0 到 10ⁿ−1 中不含连续 66 的数

用末位是否为6的两状态递推,按照 n=2 得99的示例确认零和较短数字也计入。

题目重点:用末位是否为6的两状态递推,按照 n=2 得99的示例确认零和较短数字也计入。

题意与假设

这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。

  • 计数范围是整数0≤x<10^n,而非恰好n位正整数;等价于n位补前导零字符串。
  • 禁止的是相邻两个十进制6,不是至少出现两次6;606合法。
  • n为非负整数,n=0范围仅包含0,返回1;如题面要求n≥1可收紧输入校验。

解题路线

  1. 状态A表示已构造前缀且末位不是6,B表示末位是6。初始空前缀A=1、B=0,空前缀只作为建数起点,不额外增加整数数量。
  2. 从任一合法前缀附加0/1/2/3/4/5/7/8/9共9种,得到新A=9(A+B)。附加6只能来自旧A,得到新B=A;来自旧B会生成66,必须排除。
  3. 一轮递推同时使用旧A和旧B,不能先覆盖A再算B。最后答案A+B,所有长度n补零串和范围内整数一一对应,补零不会凭空产生连续66。
  4. 也可记总数T_n=9(T_{n−1}+T_{n−2}),n≥2时成立,初值T_0=1、T_1=10;两状态写法更直接解释约束,避免初值或第二项下标写错。
  5. 若题目改为恰好n位正整数,首位0必须排除。例如n=2仅10..99共90个数,排除66后是89,不能继续答99。任意上界M还需tight状态,不能只按位数返回。
  6. 一般禁止pattern可把“已经匹配pattern的最长后缀”做有限状态机,再进行digit DP。当前66只需一位记忆,不要为了简单禁串引入指数状态。

手动走一遍

n=1:A=9、B=1,总10。n=2:A=90、B=9,总99。n=3得到981;含66的三位补零串共19个,066..966与660..669各10个,666重叠一次,1000−19=981。

Python 参考实现

下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。

代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。

"""Count integers 0 <= x < 10**n without adjacent decimal digits 66.
Use n-digit zero-padded strings, so zero and shorter integers are included.
"""

def count_without_66(n):
    if not isinstance(n, int) or isinstance(n, bool) or n < 0:
        raise ValueError("nonnegative integer digit length required")
    non_six, last_six = 1, 0
    for _ in range(n):
        non_six, last_six = 9 * (non_six + last_six), non_six
    return non_six + last_six

复杂度

执行n次常数个大整数运算;数值位数O(n),Python任意精度下不能简单把所有运算视为固定机器字成本,总位运算约O(n²)。状态数量O(1),实际整数存储O(n) bits。只需模答案时可在每次递推取模。

易错点与追问

容易失分的边界

  • 把n位正整数与0..10^n−1混淆。
  • 先覆盖A导致B引用新状态。
  • 禁止两个6而不是相邻66。
  • 大答案用32位整数溢出。

继续深挖

  • 若禁止666需要几个状态?
  • 如何求任意上界M以内的计数?
  • 恰好n位正整数如何初始化?
  • n极大且取模,怎样用矩阵快速幂?
✓已复制微信号!