问题描述
小C对“好数”非常感兴趣,她定义一个不含前导零的正整数为“好数”,如果它的所有数位最多包含两种不同的数字。例如,数字 23,2323,9,111,和 101 都是好数。现在小C想知道,从1到n之间有多少个好数。
示例
- 当 ( n = 110 ) 时,所有的1位数、2位数,以及一些3位数(如
100,101)都是好数,一共有102个。 - 当 ( n = 1000 ) 时,一共有352个好数。
- 当 ( n = 1 ) 时,只有1个好数,即1本身。
问题分析
要解决这个问题,我们需要一个高效的方法来判断一个数是否是“好数”。直接遍历从1到n的所有数字并逐一检查每个数字是否是“好数”虽然简单直接,但在n较大时效率较低。我们可以考虑以下几种优化方法:
- 预处理:预先计算出所有可能的“好数”,然后在查询时直接查找。
- 动态规划:利用动态规划的思想,逐步构建“好数”的数量。
- 数学方法:通过数学公式或组合数学的方法,直接计算出“好数”的数量。
解决方案
我们先从最直接的方法开始,即逐个检查每个数字是否是“好数”。
代码实现
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
代码解释
-
is_good_number函数:
- 这个函数用于判断一个数字是否是“好数”。
- 它将数字转换为字符串,并使用集合来存储不同的数字。
- 如果集合的长度小于等于2,则该数字是“好数”。
-
solution函数:
- 这个函数遍历从1到n的所有数字,并使用
is_good_number函数来判断每个数字是否是“好数”。 - 如果是,则计数器
count加1。
- 这个函数遍历从1到n的所有数字,并使用
-
主程序:
- 主程序调用
solution函数并打印结果,以验证代码的正确性。
- 主程序调用
优化思路
虽然上述方法可以直接解决问题,但在n较大时效率较低。为了提高效率,我们可以考虑以下优化方法:
动态规划
我们可以使用动态规划来逐步构建“好数”的数量。具体来说,我们可以定义一个二维数组 dp[i][j],表示长度为i且最高位为j的“好数”数量。
-
状态定义:
dp[i][j]表示长度为i且最高位为j的“好数”数量。
-
状态转移:
- 对于每一个长度为i的数字,我们可以选择两个不同的数字作为其数位。
- 如果最高位为j,那么第二位可以选择与j相同的数字,或者另一个不同的数字。
-
初始化:
dp[1][j] = 1,对于所有0到9的数字,长度为1的“好数”数量都是1。
-
最终结果:
- 我们需要累加所有长度不超过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
代码解释
-
状态定义:
dp[i][j][k]表示长度为i,最高位为j,已经用了k种不同数字的“好数”数量。
-
状态转移:
- 对于每一个长度为i的数字,我们可以选择两个不同的数字作为其数位。
- 如果最高位为j,那么第二位可以选择与j相同的数字,或者另一个不同的数字。
-
初始化:
dp[1][j][1] = 1,对于所有0到9的数字,长度为1的“好数”数量都是1。
-
最终结果:
- 使用深度优先搜索(DFS)来计算最终结果,确保不会超过给定的n。
总结
通过上述方法,我们可以高效地计算出从1到n之间的“好数”数量。直接遍历的方法虽然简单,但在n较大时效率较低。使用动态规划和DFS相结合的方法可以显著提高计算效率,适用于更大的输入范围。