计算小于等于 N 的疯狂整数数量
在数字世界中,有一种特别的整数,我们称之为疯狂整数。这种整数的特点是:它只包含数字 '1' 和 '2'。例如,1, 2, 11, 12, 21, 122 等都是疯狂整数。这个问题的挑战在于,给定一个整数 N,我们需要计算出所有小于或等于 N 的疯狂整数的数量。
问题描述
疯狂整数的定义非常简单:
- 每个数字只能是
1或2。 - 这些数可以是任意多位数,只要每一位上的数字都是
1或2。
例如,对于 N = 22,所有小于等于 22 的疯狂整数是:1, 2, 11, 12, 21, 22,总共有 6 个疯狂整数。
问题的挑战
问题的核心在于,如何高效地生成所有小于等于 N 的疯狂整数,并计算它们的数量。直接枚举所有数字并检查是否是疯狂整数并不高效,特别是在 N 很大的情况下。我们需要一种有效的方法来生成疯狂整数,并避免重复的计算。
解决思路
我们可以通过递归或迭代的方式生成疯狂整数。每个疯狂整数可以通过在已有的数字后面加上 1 或 2 来扩展。例如,1 可以扩展成 11 和 12,2 可以扩展成 21 和 22,以此类推。通过这种方式,我们可以不断地生成新的数字,直到生成的数字超过 N 为止。
递归方法的问题:
虽然递归方法简单直观,但它可能会面临以下问题:
- 栈溢出:当
N较大时,递归的深度可能会导致栈溢出。 - 性能低下:每次递归调用都会产生子问题,可能会导致重复计算,影响性能。
为了克服这些问题,我们可以采用迭代方法,使用队列来逐步生成所有可能的疯狂整数,避免了递归带来的复杂性和性能问题。
优化的解法:使用队列
我们使用一个队列来存储正在生成的疯狂整数,每次从队列中取出一个数字,并扩展成新的疯狂整数。每个新的数字通过将当前数字分别加上 1 和 2 来扩展。这个过程类似广度优先搜索(BFS),我们不断地从队列中取出一个数字,生成下一个数字,直到数字超过 N。
通过这种方式,我们可以高效地生成所有符合条件的疯狂整数,而不需要进行递归调用。下面是这个方法的代码实现。
优化后的代码实现
def solution(N):
# 用于存储结果的计数器
count = 0
# 使用一个队列(可以用 list 来模拟队列)来逐步构造疯狂整数
queue = [1, 2]
while queue:
num = queue.pop(0) # 获取队列中的第一个元素
# 如果当前数字大于 N,则跳过
if num > N:
continue
# 当前数字小于等于 N,计数
count += 1
# 生成下一个疯狂整数,分别在当前数字后面添加 '1' 和 '2'
queue.append(num * 10 + 1)
queue.append(num * 10 + 2)
return count
代码解析
count变量:用于存储符合条件的疯狂整数的数量。- 队列
queue:用来存储待处理的数字。初始时,我们将1和2放入队列,因为它们是最基本的疯狂整数。 - 队列操作:
- 从队列中取出第一个数字
num。 - 如果
num超过N,则跳过这个数字。 - 如果
num小于等于N,则计数器count增加 1。 - 将
num的扩展版本(num * 10 + 1和num * 10 + 2)加入队列,继续生成下一个疯狂整数。
- 从队列中取出第一个数字
- 终止条件:当队列为空时,表示所有可能的疯狂整数都已经处理完毕,算法结束。
示例测试
print(solution(22)) # 输出: 8
print(solution(5)) # 输出: 3
时间复杂度分析
- 空间复杂度:我们使用了一个队列来存储当前待生成的数字,队列的大小和
N的大小相关。在最坏情况下,队列中的数字个数与N的位数和生成的疯狂整数数量有关,因此空间复杂度是O(N)。 - 时间复杂度:在每次处理一个数字时,我们扩展它生成两个新的数字。每次扩展都会将一个数字从队列中取出并生成新的数字。由于每个数字最多只会被处理一次,所以时间复杂度是
O(2^k),其中k是数字N的位数。这个复杂度在实际应用中表现得非常高效。
结论
通过使用队列迭代生成疯狂整数的方法,我们能够高效地计算出所有小于等于给定数字 N 的疯狂整数数量。相比于递归方法,这种优化方式避免了递归的深度问题,并且大大提升了算法的性能。在实际应用中,这种方法能够处理较大的输入,并且保证高效和稳定。
这种方法利用了队列的广度优先生成策略,非常适合用于此类生成所有可能组合的问题,能够在时间和空间上提供较好的平衡。