剑指offer 43——1~n整数中1出现的次数

数字揽汐人
• 阅读 2246

本题主要在于找规律,从一个例子开始,总结出其中的规律。
<!-- more -->

原题

输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数。

例如,输入12,1~12这些整数中包含1 的数字有1、10、11和12,1一共出现了5次。

示例 1:

输入:n = 12
输出:5

示例 2:

输入:n = 13
输出:6
 ```

限制:
* 1 <= n < 2^31

原题url:https://leetcode-cn.com/problems/1nzheng-shu-zhong-1chu-xian-de-ci-shu-lcof/

## 解题

### 暴力法

一开始我的想法是计算出从 1 到 n 中,每一个数中包含的1个数,累加在一起,求得结果:

class Solution {

public int countDigitOne(int n) {
    if (n < 1) {
        return 0;
    }
    
    // 存储从1到上一个数的总结果,初始是从1到1的总结果
    int before = 1;
    int temp, count;
    for (int i = 2; i <= n; i++) {
        temp = i;
        count = 0;
        while (temp != 0) {
            // 计算当前数字的个位数是否等于1
            if (temp % 10 == 1) {
                count++;
            }
            temp /= 10;
        }

        before += count;
    }
    return before;
}

}

提示我`超出时间限制`。

接下来我想到的是,当给我们一个数 x 时,如果我知道 (x / 10) 对应的 1 的个数,那么再加上最高位 1 的个数,就得出了当前数字对应的 1 的个数。

用一个数组,记录每一个数字对应的1的个数。由此可以写出代码:

class Solution {

public int countDigitOne(int n) {
    if (n < 1) {
        return 0;
    }
    // 存储数字原本含有的1的数量
    int[] countOne = new int[n + 1];
    // 存储从1到上一个数的总结果,初始是从1到1的总结果
    int before = 0;
    int temp, count;
    for (int i = 1; i <= n; i++) {
        countOne[i] = (i % 10 == 1 ? 1 : 0) + countOne[i / 10];
        before += countOne[i];
    }
    return before;
}

}

提示我`超出内存限制`。

看来暴力求解不可取,让我们换一种思路。

### 找规律

由上面的暴力法,我们可以得知:
1. 这道题肯定不能从 1 开始慢慢算出每一个数所对应的 1 的个数,因为这样会超时。
2. 也不可以借用 n 个额外空间,因为这样会超出内存限制。

那么正确的思路就应该是,给你一个数 n ,你直接计算出从 1 到 n 的数中,所有 1 的总个数。

假设给你一个数 3210,你会如何求解呢?

我们可以把这个数进行拆分:

3210 = 3000 + 200 + 10 + 0

      = 3 * 1000 + 2 * 100 + 1 * 10 + 0 * 1

似乎没有看出什么规律,那么我们再试着求出每一位,对应的 1 的个数。

从千位 3 开始,针对 1000 ~ 1999 这 1000 个数字,千位上都是 1,因此这里有 1000 个。
百位 2,针对 100 ~ 199 这 100 个数字,百位上都是 1。一共出现了 3 组,分别是 100 ~ 199、1100 ~ 1199、2100 ~ 2199、3100 ~ 3199,一共 (4 * 100 = 400) 个。
十位 1,针对 10 ~ 19 这 10 个数字,十位上都是 1。一共出现了 32 组,但是再加上 3210 本身十位上也是 1,因此一共有 ( 32 * 10 + 1 = 321) 个。
个位 0,针对 1、11、21、...、101、111、121、...、1001、1011、1021、...、3201,每 10 个数都会出现 1 个,因此一共有 (321 * 1 = 321)个。
一共 2042 个。

我用力扣本身的测试用例进行了校验,结果是一致的。

我们总结一下上面的过程:

我们将数字 n,按照位,从低到高进行遍历。
我们假设当前位的数字为 cur,高位为 high,低位为 low,位数为 digit。比如 3210 这个数,针对百位而言,cur 是 2,high 是 3,low 是 10,digit 是 100。
针对 cur,有 3 种情况:
1、大于 1,那么此位 1 的个数就是:(high + 1) * digit
2、等于 1,那么此位 1 的个数就是:high * digit + low + 1
3、小于 1(也就是等于 0 ),那么此位 1 的个数就是:high * digit


接下来我们看看代码:

class Solution {

public int countDigitOne(int n) {
    if (n < 1) {
        return 0;
    }
    
    // 高位
    int temp = n;
    // 低位
    int low = 0;
    // 1出现的总次数
    int total = 0;
    // 当前位数,比如个位时为1,十位时为10,百位时为100
    int pow = 1;
    while (temp != 0) {
        // 当前位上的数字
        int num = temp % 10;
        // 剩下的高位
        temp = temp / 10;
        // 如果当前位上的数字是0
        if (num == 0) {
            // 只加上高位对应
            total += temp * pow;
        } 
        // 如果当前位上的数字是1
        else if (num == 1) {
            // 加上高位对应的数字,和低位的所有,再加1(它本身)。
            // 比如10,虽然低位是0,但本身还有1
            total += temp * pow + low + 1;
        }
        // 如果当前位上的数字大于1
        else {
            // 那么高位+1
            total += (temp + 1) * pow;
        }

        // 低位
        low += num * pow;
        // 进入下一位
        pow = pow * 10;
    }

    return total;
}

}

提交OK。

我们来分析一下复杂度:
* 时间复杂度 `O(log N)` : 循环内的计算操作使用 O(1) 时间,循环次数为数字 n 的位数,即 log 以10为底 n,因此总时间为 `O(log N)`。
* 空间复杂度 `O(1)` : 只有几个变量使用常数大小的额外空间。

## 总结
    
以上就是这道题目我的解答过程了,不知道大家是否理解了。本题主要在于找规律,从一个例子开始,总结出其中的规律。

有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。

[https://death00.github.io/](https://death00.github.io/)

公众号:健程之道

![](https://imgkr.cn-bj.ufileos.com/01be7a29-45a6-4739-ae17-807c175741db.jfif)
点赞
收藏
评论区
推荐文章
DaLongggggg DaLongggggg
4年前
python刷题-特殊回文数
问题描述  123321是一个非常特殊的数,它从左边读和从右边读是一样的。  输入一个正整数n,编程求所有这样的五位和六位十进制数,满足各位数字之和等于n。输入格式  输入一行,包含一个正整数n。输出格式  按从小到大的顺序输出满足条件的整数,每个整数占一行。样例输入52样例输出899998989989998899数据规模和约定 
DaLongggggg DaLongggggg
4年前
python刷题-最大最小公倍数
问题描述已知一个正整数N,问从1~N中任选出三个数,他们的最小公倍数最大可以为多少。输入格式输入一个正整数N。输出格式输出一个整数,表示你找到的最小公倍数。样例输入9样例输出504数据规模与约定1<N<106。Nint(input())Min1ifN<2:print(N)elifN%2
DaLongggggg DaLongggggg
4年前
python-阶乘计算
问题描述  输入一个正整数n,输出n的值。  其中n123…n。算法描述  n可能很大,而计算机能表示的整数范围有限,需要使用高精度计算的方法。使用一个数组A来表示一个大整数a,A0表示a的个位,A1表示a的十位,依次类推。  将a乘以一个整数k变为将数组A的每一个元素都乘以k,请注意处理相应的进位。  首先将a设为1,然后乘2,
DaLongggggg DaLongggggg
4年前
python刷题-进制转换
十六进制转八进制问题描述  给定n个十六进制正整数,输出它们对应的八进制数。输入格式  输入的第一行为一个正整数n(1<n<10)。  接下来n行,每行一个由0~9、大写字母A~F组成的字符串,表示要转换的十六进制正整数,每个十六进制数长度不超过100000。输出格式  输出n行,每行为输入对应的八进制正整数。  【注意】  输入的十六进制数不会有
DaLongggggg DaLongggggg
4年前
python刷题-数列特征
问题描述给出n个数,找出这n个数的最大值,最小值,和。输入格式第一行为整数n,表示数的个数。第二行有n个数,为给定的n个数,每个数的绝对值都小于10000。输出格式输出三行,每行一个整数。第一行表示这些数中的最大值,第二行表示这些数中的最小值,第三行表示这些数的和。样例输入513245样例输出5211数据规模与约定1<n<10000。
DaLongggggg DaLongggggg
4年前
python刷题-查找整数
问题描述给出一个包含n个整数的数列,问整数a在数列中的第一次出现是第几个。输入格式第一行包含一个整数n。第二行包含n个非负整数,为给定的数列,数列中的每个数都不大于10000。第三行包含一个整数a,为待查找的数。输出格式如果a在数列中出现了,输出它第一次出现的位置(位置从1开始编号),否则输出1。样例输入61948399样例输
Stella981 Stella981
3年前
Python_for 和 while 求n!
for和while求n!nint(input("请输入一个整数您将得到阶乘"))sum1foriinrange(1,n1):sumiprint("for循环的阶乘:",sum)sum1i1while(i<
Wesley13 Wesley13
3年前
1187.年龄最小的三个职工
题目描述:职工有职工号,姓名,年龄.输入n个职工的信息,找出3个年龄最小的职工打印出来。  输入:输入第一行包括1个整数N,1<N<30,代表输入数据的个数。接下来的N行有N个职工的信息:包括职工号(整数),姓名(字符串,长度不超过10),年龄(1<age<100)。输出:可能有多
Wesley13 Wesley13
3年前
Codevs 1159最大全0子矩阵
题目描述Description在一个0,1方阵中找出其中最大的全0子矩阵,所谓最大是指O的个数最多。输入描述InputDescription输入文件第一行为整数N,其中1<N<2000,为方阵的大小,紧接着N行每行均有N个0或1,相邻两数间严格用一个空格隔开。输出描述OutputDescription
Stella981 Stella981
3年前
LeetCode 5561. 获取生成数组中的最大值
文章目录1\.题目2\.解题1\.题目给你一个整数n。按下述规则生成一个长度为n1的数组nums:nums00nums11当2<2i<n时,nums2inumsi
Wesley13 Wesley13
3年前
1050 螺旋矩阵
1050 螺旋矩阵 (25分)本题要求将给定的 N 个正整数按非递增的顺序,填入“螺旋矩阵”。所谓“螺旋矩阵”,是指从左上角第1个格子开始,按顺时针螺旋方向填充。要求矩阵的规模为 m 行 n 列,满足条件:m×n 等于 N;m≥n;且 m−n 取所有可能值中的最小值。输入格式:输入在第1行中给出一个正整数 N,第2行给出 N
数字揽汐人
数字揽汐人
Lv1
十步杀一人,千里不留行。——李白
文章
2
粉丝
0
获赞
0