问题描述
小C发现了一类特殊的整数,他称之为“疯狂整数”。疯狂整数是指只包含数字 '1' 和 '2' 的整数。举个例子,数字 1、2 和 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();