算法分析与设计(一)

算法分析与设计(一)

1、基础知识

时间复杂度和空间复杂度: 点击跳转文章(转载)

2、统计数字问题

(1). 问题描述
一本书的页码从自然数1 开始顺序编码直到自然数n。书的页码按照通常的习惯编排,每个页码都不含多余的前导数字0。例如,第6 页用数字6 表示,而不是06 或006 等。数字计数问题要求对给定书的总页码n,计算出书的全部页码中分别用到多少次数字0,1, 2,…,9。

(2). 算法设计
给定表示书的总页码的10 进制整数n (1≤n≤10 ) 。编程计算书的全部页码中分别用到多少次数字0,1,2,…,9。

解析:
具体的算法思想是:

说明:n为一个自然数,len表示为十进制数的位数(如1234,则n为1234,len为4)。

对于一个len位整数,我们可以把0到n之间的n+1个整数从小到大这样来排列(补0):

000......0
.............

099......9

100......0

100......1
.............
199......9
200......0

.............
299......9

300......0
..........

n

如上所示一直排到自然数n。对于从0到099…9这个区间来说,抛去最高位的数字不看,不难验证,0到9每个数字出现的次数都等于(len-1)*10的(len-2)次方,
而目标数字n的最高位数p就是 0到099…9这样的区间 的个数,用(len-1)*10的(len-2)次方 *p 在加上这些区间最高位数出现的次数,我们就已经把 0到 n除最高位数其他位数变为0的数x(如n为3929,则x为3000) -1 之间各个数字出现的次数统计出来了。
接下来我们只需将x到n之间数据统计出来即可。我们需把n-x这个数再次递归操作。
不过在进入递归之前,我们要先把最高位数出现的n-x+1次补上,还要判断n-x的位数是否等于len-1,不是的话说明n是例如10010这样的数,要把len-(n-x这个数的位数)-1个0补上。

例子说明:

  对于一个数字34567,我们可以这样来计算从1到34567之间所有数字中每个数字出现的次数:
  从0到9999,这个区间的每个数字的出现次数可以使用原著中给出的递推公式,即每个数字出现4000次((5-1)* 10的3次方 )。
  从10000到19999,中间除去万位的1不算,又是一个从0000到9999的排列,
  这样的话,从0到34567之间的这样的区间共有3个。
  所以从00000到29999之间除万位外每个数字出现次数为3*4000次。
  然后再统计万位数字,每个区间长度为10000,
  所以0,1,2在万位上各出现10000次。
  而3则出现4567+1=4568次。
  之后,抛掉万位数字,对于4567,再使用上面的方法计算,一直计算到个位即可。

在结果统计好之后。还有最后一步,就是减去补上的0.当n=21时,要减去的0有:100(红色部分)+101(黄色部分)

在这里插入图片描述

当n=123时,要减去的0有:100(红色部分)+101(黄色部分)+10^2(灰色部分)
在这里插入图片描述
可知减少的0的数量为 100 + ······ + 10 n

源码:

#include<iostream>
#include<cstdio>
#include<cmath>

using namespace std;

int c[10]; // c数组存储结果

int weishu(int n) {
    int weishu = 1;
    while (n / 10 >= 1) { //int除法向下取整
        weishu++;
        n = n / 10;
    }
    return weishu;
}

int zuigao(int n, int len) {
    int num = round(pow(10.0, len - 1)); //round 函数四舍五入 ,确保结果无误。10的位数减一次方
    return n / num; 
}
 
int yushu(int n,int len) {
    int num = round(pow(10.0, len - 1));
    return n % num;
}


void solve(int n) {
    //数n的位数
    int len = weishu(n);
    //最高位的值
    int p = zuigao(n, len);
    //计算忽略首为数字后(除了首字母最高位和p一样的),1到10出现的次数
    for (int i = 0; i < 10; i++) {
        c[i] += p * (len - 1) * (int)round(pow(10.0, len - 2)); 
        // 区间: 长度为len-1的 0···00 到 长度为 len-1的 9···99 出现次数为 len-1 * 10 的 len - 2 次方“(len - 1) * (int)round(pow(10.0, len - 2))”
        // 共有p个区间。
    }
    // 计算0到p,首位数字出现的次数,并加入结果
    for (int i = 0; i < p; i++) {
        c[i] += (int)round(pow(10.0, len - 1));
    }

    //求n去掉最高位数后的数,(如1229去掉最高位为229)
    int t = yushu(n , len);
    if (t == 0) {//如果t为0
        c[p]++;//最高位加1
        c[0] += len - 1;//0位加len-1
        return;
    }
    int lenT = weishu(t);
    if (lenT != len - 1) {//若像10010这种情况,中间2个0也要相应的处理
        c[0] += (len - lenT - 1) * (t + 1);//若像10010这种情况, 因为10000到10010,中间的数的2个0 都要处理,所以*t+1
    }
    c[p] += 1 + t; //最高位的数目补一下 如n=10010,10000到10010的最高位1补上 11
    return solve(t); //把n去掉最高位数后的数进入下一次递归
}

int main() {
    int n;
    while (cin >> n) {
        fill(c, c + 10, 0); //把数组填充满为0
        int len = weishu(n);
        solve(n);

        for (int i = 0; i < len; i++) {
            c[0] -= (int)round(pow(10.0, i)); //减去补上的0
        }

        for (int i = 0; i < 10; i++) {
            cout << i << " : " << c[i] << endl;
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值