青训营X豆包MarsCode技术训练营第四课 | 豆包MarsCode AI刷题

41 阅读7分钟

方向一:学习方法与心得

一、题目展示

问题描述

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

例如:当n=110n=110时,所有的1位数、2位数,以及一些3位数(如 100101)都是好数,一共有102个。


测试样例

样例1:

输入:n = 110
输出:102

样例2:

输入:n = 1000
输出:352

样例3:

输入:n = 1
输出:1

二、题目分析

1、题目理解

  • 本题主要围绕着 “好数” 的定义展开,要求找出从 1 到给定的整数n之间 “好数” 的个数。
  • “好数” 的定义为:一个不含前导零的正整数,其所有数位最多包含两种不同的数字。例如 23(只有 2 和 3 两种数字)、2323、9(只有一种数字也算符合要求)、111、101 等都是好数。

2、解题思路探讨

  • 暴力枚举法思路

    • 可以从 1 开始,依次遍历到n的每一个整数。
    • 对于每个整数,判断它是否为 “好数”。判断方法是统计该整数各个数位上出现的不同数字的种类,如果种类不超过两种,则它是 “好数”。
    • 最后统计出 “好数” 的总数。但这种方法在n较大时,时间复杂度会很高,因为需要对每个数都进行判断操作,时间复杂度大致为 O (n * k),其中k是判断一个数是否为 “好数” 的操作复杂度(一般与数的位数有关)。
  • 数位动态规划思路(可能更高效的方法)

    • 考虑到每个数的数位情况,可以从高位到低位逐步构建数字并判断是否为 “好数”。
    • 定义状态:例如可以定义dp[i][j][k]表示构建到第i位数字,已经使用了j种数字(j取值为 0、1、2),当前构建的数字是否小于等于给定数字n的前i位数字(k取值为 0 或 1)的情况下的 “好数” 个数。
    • 状态转移:根据当前位要填的数字以及前面的状态来更新下一位的状态。比如,如果当前位填的数字与前面已用的数字种类相同,那么j的状态可能不变;如果填了新的数字种类,j的值可能要更新。同时,要根据填的数字与n对应位数字的大小关系来正确更新k的值。
    • 最终答案:通过合理的状态转移和初始化,最后可以根据定义的状态得到从 1 到n的 “好数” 个数,一般是通过对最后几位的状态进行求和等操作得到。这种方法的时间复杂度相对暴力枚举法会大大降低,通常可以达到多项式时间复杂度,比如 O (d * 3^d),其中dn的数字位数。

3、输入输出分析

  • 输入

    • 题目中明确给出输入为一个整数n,代表要统计从 1 到n之间的 “好数” 个数的范围上限。
  • 输出

    • 输出为一个整数,即从 1 到n之间满足 “好数” 定义的整数的个数。

4、示例解读

  • 在样例 1 中,当n = 110时:

    • 1 位数有 9 个(1 - 9)都是好数。
    • 2 位数从 10 到 99,对于每一位数都可以选择两种数字来构成好数(比如十位选 1,个位可以选 0 或者 1 等多种组合),通过一定的组合计算可以得出 2 位数中的好数个数。
    • 再加上 3 位数中的 100 和 101 这两个好数,总共得到 102 个好数。
  • 在样例 2 中,当n = 1000时:

    • 同样先计算 1 位数和 2 位数中的好数个数,方法类似样例 1。
    • 然后考虑 3 位数的情况,按照前面提到的数位动态规划或者其他合理的计数方法来统计出 3 位数中满足条件的好数个数,最后汇总得到 352 个好数。
  • 在样例 3 中,当n = 1时,显然 1 本身就是一个好数,所以输出为 1。

三、代码展示

def solution(n: int) -> int:
    def is_good_number(num: int) -> bool:
        # 将数字转换为字符串
        s = str(num)
        # 使用集合来存储不同的字符
        unique_chars = set(s)
        # 检查集合的大小是否小于等于2
        return len(unique_chars) <= 2
    
    count = 0
    # 遍历从1到n的所有数字
    for i in range(1, n + 1):
        if is_good_number(i):
            count += 1
    
    return count

if __name__ == '__main__':
    print(solution(110) == 102)
    print(solution(1000) == 352)
    print(solution(1) == 1)

四、结果分析

1、整体功能概述

代码的主要目的是实现一个函数 solution,用于计算从 1 到给定整数 n 之间满足特定条件(即所谓 “好数” 条件)的数字的个数。然后通过在 __main__ 部分的测试用例,验证函数对于不同输入值的正确性。

2、函数 solution 分析

(1). 内部函数 is_good_number

  • 功能

    • 这个函数用于判断一个给定的整数 num 是否为 “好数”。判断的依据是将整数转换为字符串后,检查字符串中不同字符的数量是否小于等于 2。
  • 实现细节

    • 首先,通过 str(num) 将输入的整数 num 转换为字符串形式,以便后续处理各个数位。
    • 接着,创建一个空集合 unique_chars,利用集合的特性(元素唯一性),将字符串 s 中的每个字符依次添加到集合中。在这个过程中,集合会自动去除重复的字符。
    • 最后,通过 len(unique_chars) <= 2 判断集合中不同字符的数量是否满足 “好数” 的定义要求,即最多包含两种不同的数字,并返回相应的布尔值结果。

(2). 主循环部分

  • 功能

    • 在 solution 函数的主体部分,通过一个循环遍历从 1 到 n 的所有整数,并调用 is_good_number 函数来判断每个整数是否为 “好数”。如果是 “好数”,则将计数器 count 的值加 1。
  • 实现细节

    • 使用 range(1, n + 1) 生成一个从 1 到 n 的整数序列,用于遍历所有可能的数字。
    • 对于每个数字 i,调用 is_good_number(i) 进行判断。如果返回值为 True,说明 i 是 “好数”,此时执行 count += 1,将 “好数” 的计数增加 1。

(3). 返回值

  • 循环结束后,函数 solution 返回计数器 count 的值,这个值就是从 1 到 n 之间 “好数” 的总个数。

3、__main__ 部分分析

  • 在 if __name__ == '__main__': 语句块内:

    • 分别对函数 solution 传入不同的测试值(110、1000、1),并通过 print(solution(xxx) == yyy) 的形式将函数的返回值与预期的正确结果(分别为 102、352、1)进行比较,然后打印出比较结果。这一步骤的目的是验证函数 solution 在不同输入情况下的正确性,以便在开发和调试过程中快速发现函数可能存在的问题。

4、代码优缺点

优点

  • 代码结构清晰:通过定义内部函数 is_good_number 来处理 “好数” 的判断逻辑,使得主函数 solution 的逻辑更加清晰,易于理解和维护。
  • 简单直接:采用了较为直接的遍历和判断方法来实现功能,对于理解基本的编程逻辑和解决此类计数问题有很好的示范作用,尤其适合初学者理解如何根据特定条件对一系列数字进行筛选和计数。

缺点

  • 效率问题:对于较大的 n 值,这种逐个遍历并判断每个数字是否为 “好数” 的方法可能会比较耗时。因为每次判断一个数字是否为 “好数” 都需要将其转换为字符串并处理字符串中的每个字符,时间复杂度相对较高,大致为 O (n * k),其中 n 是输入的上限值,k 是判断一个数字是否为 “好数” 所涉及的操作复杂度(与数字的位数有关)。在实际应用中,如果对性能要求较高,可能需要考虑更高效的算法,如采用数位动态规划等方法来优化。