LeetCode每日一题 233. 数字 1 的个数

167 阅读2分钟

这是我参与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=1

    • c前面,可以选择的范围是0~ab-1,c后面可以选择的范围是0~9999;c前面还可以选择ab,后面选择的范围是0~defg
  • 如果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;
    }
}

注意: 循环中的leftright指的是,枚举到的当前这一位前面的数字和后面的数字;当然也不用每次都去计算一下left,我们只需要维护一个外部的left,每次用迭代来计算即可;不过right还是得遍历来算,所以时间复杂夫并没有减少;-_- 类似于这种数字规律相关的可以归类到数位DP这一类问题中,先将每一位数字抠出来,再针对具体问题,来具体分析;

image-20210813093535756