(473)疯狂整数的统计 | 豆包MarsCode AI刷题

82 阅读3分钟

问题描述

小C发现了一类特殊的整数,他称之为“疯狂整数”。疯狂整数是指只包含数字 '1' 和 '2' 的整数。举个例子,数字 12 和 121 都是疯狂整数。

现在,给定一个整数 N,你的任务是计算出所有小于或等于 N 的非负疯狂整数的数量。

测试样例

样例1:

输入:N = 21
输出:5

样例2:

输入:N = 50
输出:6

样例3:

输入:N = 5
输出:2

解题思路

本题的要求是找出0到给定整数N(包括N)的范围内的仅包含数字1或2的所有整数的个数。具体需求了解之后首先跳入脑海的就是暴力解法,即遍历从0到N的所有整数,判断当前数字的每个组成数字是否为1或2,如果是则将总数加1,如果不是则继续向后查找。这种方法显然是最简便实现的方法,但是也浪费了很多时间在不相关的整数判断上,比如122和211之间就相差了89个数字,这只是在数量级小的情况下,当数量级一旦变大时一些不必要的整数就会成倍增长,所耗费的时间同样会成倍增长。

仔细想一下,既然需求是0到N范围内的由1或2组成的数字,那么我们可以直接生成由1和2组成的数字然后进行范围判断,这样就可以保证我们的所有整数都是“疯狂整数”,省去了“疯狂整数”的判断,那么我们就会发现这个数字生成的规律,如下所示:

                       1                                      2
                /             \                        /              \
             11                 12                  21                  22
         /       \           /       \          /        \          /        \
       111       112       121       122       211       212       221       222
      /   \     /   \     /   \     /   \     /   \     /   \     /   \     /   \ 
    1111 1112 1121 1122 1211 1212 1221 1222 2111 2112 2121 2122 2211 2212 2221 2222
                                        ......

通过观察上面的图示可以看出,每个长度范围内的由1或2组成的数字都是由上一个数字的末尾添加1或2得到的,以此类推。因此我们可以联想到使用递归可以轻松实现这个“疯狂整数”的生成,通过判断当前数字的长度是否超过N的长度来控制递归的深度,然后将当前数字和N进行大小判断,如果小于等于N,则计数器+1,同时在当前整数字符串的末尾分别添加"1"和"2"递归生成下一个“疯狂整数”并进行判断,以空字符串作为递归起始点逐个生成“疯狂整数”。这种方法的时间复杂度为O(2^n),n是N的长度,每个位置有1或2两种可能。

思路清晰之后再开始代码编写就会顺畅很多。以下是我的代码实现:

代码详解

function solution(N) {
    let count = 0;
    function num(curNum) {
        // 如果当前数字的长度超过 N 的长度,停止生成
        if (curNum.length > N.toString().length) {
            return;
        }
        // 如果当前数字是有效的疯狂整数,检查是否小于等于 N
        if (curNum && parseInt(curNum) <= N) {
            count++;
        }
        // 递归生成下一个疯狂整数
        num(curNum + "1");
        num(curNum + "2");
    }
    num("");
    return count;
}

function main() {
    console.log(solution(21) === 5);
    console.log(solution(50) === 6);
    console.log(solution(5) === 2);
}
main();