这是我参与8月更文挑战的第13天,活动详情查看:8月更文挑战
233. 数字 1 的个数
给定一个整数 n,计算所有小于等于 n 的非负整数中数字 1 出现的个数。
示例 1:
输入:n = 13
输出:6
示例 2:
输入:n = 0
输出:0
方法一
计数类问题:
我们只需要算出每一位上的1分别会出现的次数,最后把各个位上出现1的次数相加,就是1~n中,所有1出现的次数了;
方便起见,我们把每一位上的数字扣下来放到数组中,在遍历每一位数字时,有如下的计算规则:
假设给定的数为abcdefg
-
当枚举到
c时, -
如果
c=0- c前面,我们可以选择的范围是
0~ab-1,c后面可以选择的范围是0~9999
- c前面,我们可以选择的范围是
-
如果
c=1- c前面,可以选择的范围是
0~ab-1,c后面可以选择的范围是0~9999;c前面还可以选择ab,后面选择的范围是0~defg
- c前面,可以选择的范围是
-
如果
c>1,前面可以选择的范围是0~ab,后面范围是0~9999
根据上述规律,枚举每一位,最后相加即可;
class Solution {
public int countDigitOne(int n) {
ArrayList<Integer> nums = new ArrayList<>();
while(n != 0) {
nums.add(n % 10);
n /= 10;
}
int res = 0;
Collections.reverse(nums);
for (int i = 0; i < nums.size(); i ++ ) {
int left = 0, right = 0, j = i + 1;
while(j < nums.size()) right = right * 10 + nums.get(j ++);
j = 0;
while(j < i) left = left * 10 + nums.get(j ++);
if (nums.get(i) == 0) {
res += left * (int)Math.pow(10, nums.size() - i - 1);
}
else if (nums.get(i) > 1) {
res += (left + 1) * (int)Math.pow(10, nums.size() - i - 1);
}else {
res += left * (int)Math.pow(10, nums.size() - i - 1) + right + 1;
}
}
return res;
}
}
注意: 循环中的left,right指的是,枚举到的当前这一位前面的数字和后面的数字;当然也不用每次都去计算一下left,我们只需要维护一个外部的left,每次用迭代来计算即可;不过right还是得遍历来算,所以时间复杂夫并没有减少;-_- 类似于这种数字规律相关的可以归类到数位DP这一类问题中,先将每一位数字抠出来,再针对具体问题,来具体分析;
