1

题目大意:

给出一个数字N,求1~N的所有数字中出现1的个数

算法思路:

本人采用了暴力遍历求解,但是只得了22分,参考了算法笔记的思路,直接写在下面考虑30710这个数:

首先考虑末尾为0

我们计算末尾有多少个数是覆盖了1的,那么末尾为1的时候前面是只能取0~3070,因为如果取3071,那么就是30711>30710了,共有3071种选择

然后考虑倒数第二位1

它为1,那么前面为0~306的时候,后面一位随便取都行,当前面为307时,前四位为3071,末尾只能取0,那么有306*10+1种取法

然后考虑倒数第三位

它为7,如果它为1的话,前面后面都可以随便取,因为它为7比1要大,只要他们组成的数不大于30710,都是有效的,那么取法共有$(30+1)*10^2$种

同理倒数第四位为0

那要令它为1,前面只能取0~2,后面可以任取,共有$3*10^3$种

最后倒数第五位

它是3,比1要大,所以它为1时,后面可以任取,共有$10^4$种

总结

如果把某个数左边取为leftNum,右边取为rightNum,那么得到的结果有三种可能,expo是当前数字的位数次方(例如123中的2,其位数为1,对应的expo就为10^1);
1.当前位为0,count += leftNum * expo;
2.当前位为1,count += leftNum*expo+rightNum+1;
3.当前为>1,count += (leftNum+1)*expo

注意点:

1、经过测试,PAT的数据最大数字不超过5位数字,所以数组开到4就好了。

提交结果

image.png

AC代码:

#include <cstdio>
#include <algorithm>

using namespace std;

int a[4] = {};
int main(){
    int N,n;
    scanf("%d",&N);
    n = N;
    int index = 0;
    while(N!=0){
        a[index++] = N%10;
        N /= 10;
    }
    // 逆置
    for (int i = 0; i < index/2; ++i) {
        swap(a[i],a[index-i-1]);
    }
    int count = 0;//统计1的个数
    int expo = 1;// a[i]对应的10^i
    int leftNum = n/10;
    int rightNum = 0;
    for (int i = index-1; i >= 0; --i) {
        if(a[i]>1){
            // 当前位大于1
            count += (leftNum+1)*expo;
        } else if(a[i]==1){
            count += leftNum*expo+rightNum+1;
        } else {
            count += leftNum * expo;
        }
        expo *= 10;
        rightNum = n%expo;
        leftNum /= 10;
    }
    printf("%d",count);
    return 0;
}

乔梓鑫
569 声望17 粉丝

主要分享个人学习经验和心得