题解:小C的好数

151 阅读4分钟

问题描述

小C对“好数”非常感兴趣,她定义一个不含前导零的正整数为“好数”,如果它的所有数位最多包含两种不同的数字。例如,数字 2323239111,和 101 都是好数。现在小C想知道,从1到n之间有多少个好数。

示例

  • 当 ( n = 110 ) 时,所有的1位数、2位数,以及一些3位数(如 100101)都是好数,一共有102个。
  • 当 ( n = 1000 ) 时,一共有352个好数。
  • 当 ( n = 1 ) 时,只有1个好数,即1本身。

问题分析

要解决这个问题,我们需要一个高效的方法来判断一个数是否是“好数”。直接遍历从1到n的所有数字并逐一检查每个数字是否是“好数”虽然简单直接,但在n较大时效率较低。我们可以考虑以下几种优化方法:

  1. 预处理:预先计算出所有可能的“好数”,然后在查询时直接查找。
  2. 动态规划:利用动态规划的思想,逐步构建“好数”的数量。
  3. 数学方法:通过数学公式或组合数学的方法,直接计算出“好数”的数量。

解决方案

我们先从最直接的方法开始,即逐个检查每个数字是否是“好数”。

代码实现

def is_good_number(num):
    digits = set(str(num))
    return len(digits) <= 2

def solution(n: int) -> int:
    count = 0
    for i in range(1, n + 1):
        if is_good_number(i):
            count += 1
    return count

if __name__ == '__main__':
    print(solution(110) == 102)  # 输出: True
    print(solution(1000) == 352) # 输出: True
    print(solution(1) == 1)      # 输出: True

代码解释

  1. is_good_number函数

    • 这个函数用于判断一个数字是否是“好数”。
    • 它将数字转换为字符串,并使用集合来存储不同的数字。
    • 如果集合的长度小于等于2,则该数字是“好数”。
  2. solution函数

    • 这个函数遍历从1到n的所有数字,并使用is_good_number函数来判断每个数字是否是“好数”。
    • 如果是,则计数器count加1。
  3. 主程序

    • 主程序调用solution函数并打印结果,以验证代码的正确性。

优化思路

虽然上述方法可以直接解决问题,但在n较大时效率较低。为了提高效率,我们可以考虑以下优化方法:

动态规划

我们可以使用动态规划来逐步构建“好数”的数量。具体来说,我们可以定义一个二维数组 dp[i][j],表示长度为i且最高位为j的“好数”数量。

  1. 状态定义

    • dp[i][j] 表示长度为i且最高位为j的“好数”数量。
  2. 状态转移

    • 对于每一个长度为i的数字,我们可以选择两个不同的数字作为其数位。
    • 如果最高位为j,那么第二位可以选择与j相同的数字,或者另一个不同的数字。
  3. 初始化

    • dp[1][j] = 1,对于所有0到9的数字,长度为1的“好数”数量都是1。
  4. 最终结果

    • 我们需要累加所有长度不超过n的“好数”数量。

代码实现

def count_good_numbers(n: int) -> int:
    s = str(n)
    k = len(s)
    
    # dp[i][j][k] 表示长度为i,最高位为j,已经用了k种不同数字的“好数”数量
    dp = [[[0 for _ in range(3)] for _ in range(10)] for _ in range(k + 1)]
    
    # 初始化
    for j in range(1, 10):
        dp[1][j][1] = 1
    
    # 填充dp表
    for i in range(2, k + 1):
        for j in range(10):
            for d in range(10):
                if d != j:
                    dp[i][j][2] += dp[i-1][d][1]
                else:
                    dp[i][j][1] += dp[i-1][d][1]
                    dp[i][j][2] += dp[i-1][d][2]
    
    # 计算最终结果
    def dfs(pos, limit, used):
        if pos == 0:
            return 1
        res = 0
        up = int(s[k - pos]) if limit else 9
        for d in range(up + 1):
            if used < 2 or (used == 2 and d in [int(s[k - pos]), int(s[k - pos - 1])]):
                res += dfs(pos - 1, limit and d == up, used + (1 if d not in [int(s[k - pos]), int(s[k - pos - 1])] else 0))
        return res
    
    return dfs(k, True, 0)

if __name__ == '__main__':
    print(count_good_numbers(110) == 102)  # 输出: True
    print(count_good_numbers(1000) == 352) # 输出: True
    print(count_good_numbers(1) == 1)      # 输出: True

代码解释

  1. 状态定义

    • dp[i][j][k] 表示长度为i,最高位为j,已经用了k种不同数字的“好数”数量。
  2. 状态转移

    • 对于每一个长度为i的数字,我们可以选择两个不同的数字作为其数位。
    • 如果最高位为j,那么第二位可以选择与j相同的数字,或者另一个不同的数字。
  3. 初始化

    • dp[1][j][1] = 1,对于所有0到9的数字,长度为1的“好数”数量都是1。
  4. 最终结果

    • 使用深度优先搜索(DFS)来计算最终结果,确保不会超过给定的n。

总结

通过上述方法,我们可以高效地计算出从1到n之间的“好数”数量。直接遍历的方法虽然简单,但在n较大时效率较低。使用动态规划和DFS相结合的方法可以显著提高计算效率,适用于更大的输入范围。