面试题解(5):给出一个数n,O(1)求解1到n这些数中1出现的次数

本文探讨了如何在O(1)的时间复杂度内求解1至n这些数中数字1出现的总次数。通过分析数字规律,采用数学统计方法,提出了一种高效算法,并给出了具体的实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

    好久没更新博客了,今天也来客博一下。
    前两天在首页看到题目:给出一个数n,O(1)求解1到n这些数中1出现的次数。

 

    看到这个题目,联想到另一个题目:给出一个数n,求1到n这些数之和.哇靠,小学算术题,这不是侮辱咱的智商嘛~计算机最擅长干重复的劳动了,于是乎,潇洒地抖出了下面code,把循环交给计算机去做:

1Int32 sum = 0;
2Int32 number = 100;
3for (Int32 i = 1; i <= number; i++)
4    sum += i;


    OK,结果是正确的,思路相当地清晰,完了吗?没完!为了显示洒家精通C#,咱把循环改造下:

1 for (Int32 i = 1; i <= number; sum += i++) ;


    结果依然是正确的,完了吗?完了!不过是完蛋的“完”.难道我们一定要让自己的代码使别人费解,来显示我们多牛X吗!难道我们一定要让计算机拼命地干傻活儿,来证明现在处理器的性能多NB吗!为什么我们不把能做的做好,剩下的...让那坨废铁干它该干的活儿:

1 sum = number * (1 + number) / 2;

 

 

    扯远了,拉回来说“正”题:给出一个数n,求解1到n这些数中1出现的次数。
    最清晰直观的做法,就是遍历这N个数,便利N个数中的数位,累计求出1出现的次数;至于所谓的递归,没去看,也懒得去看;这里,我们先分析下这些阿拉伯数字的规律,然后根据出现1的概率来进行求解
(1) 当n=9时,我们考虑0,1,2...9(共10个数字),我们不难得出结论:字符'0','1'...'9'出现的概率个占1/10(即0.1);
(2) 当n=99时,我们考虑0,1,2...99(共100个数字),十位上,'1'出现的概率个占1/10;个位上,'1'出现的概率也为1/10, 于是1出现的次数=0.1*100 + 0.1 * 100 = 20;
(3) 当n=100000000时,我们考虑0,1,2...100000000(共100000001个数字),最高位上,'1'只可能出现1次;其余各位上,'1'出现的概率各占1/10, 于是1出现的次数=1 + (0.1*8) * 100000000 = 80000001;

 

    当然,上面都是列举的最简单的例子,但我们不难看出,'1'的出现概率的确是存在规律的。下面我们来看更复杂的例子('0','1'...'9',下面统称为阿拉伯数字符):设n=XYZ(其中X、Z表示零个或多个阿拉伯数字符,Y为一个阿拉伯数字),这样XYZ就可以表示任意数字了,例如n=5可以表示为:X=Z="",Y=5;n=123456可以表示成:X="",Y=1,Z=23456或X=12345,Y=6,Z=""等6种方式。
    这里我取这种表达方式,是为了从Y上得出一个通用的统计字符出现概率的计算方法。Y为一个阿拉伯数字,所以其只能取0,1...9中的一个数字,其存在如下三种可能:
(1) Y<1
(2) Y=1
(3) Y>1


    以n=123Y45(X=123,Y在百位上,可以任意取值,Z=45)来分别讨论上面的三种情况:
(1) Y<1:(此时Y只能取0值了),我们可以把区间[0..n]中的n+1个数分成两段区间来分析,[000000..122999],即[000000..122999],即[000000...(X-1)999],
[123000..123045],即[X000..XYZ],
    其中第一段区间中Y位(百位)上,共(X * Pow(10, Length(Z)+1))个数字,出现'1'的概率占1/10,而第二段区间中,Y位上出现'1'的概率为0,因此可以得出结论:Y<1时,'1'在该位上出现的数量=0.1 * (X * Pow(10, Length(Z)+1))

(2) Y=1:(此时Y只能取1值了),我们可以把区间[0..n]中的n+1个数分成三段区间来分析,[000000..122999],即[000000..122999],同(1),Y位上出现'1'的概率为1/10;
[123000..123099],即[X000..X099],同(1),Y为上出现'1'的概率为0;
[123100..123145],即[XY00..XYZ],Y上出现'1'的概念为1,此区间工(Z+1)个数字
因此可以得出结论:Y=1时,'1'在该位上出现的数量=0.1 * (X * Pow(10, Length(Z)+1)) + (Z + 1)

(3) Y>1:(此时Y只能取2..9中的任意一个值了),我们可以把区间[0..n]中的n+1个数分成四段区间来分析,
[000000..122999],即[000000..(X-1)999],同(1),Y位上出现'1'的概率为1/10;
[123000..123099],即[X000..X099],同(1),Y位上出现'1'的概率为0;
[123100..123199],即[X100..X199],Y上出现'1'的概念为1,此区间共Pow(10, Length(Z))个数字;
[123200..123Y45],即[X200..XYZ],此时Y恒大于0,Y位上出现'1'的概率为0;
因此可以得出结论:Y>1时,'1'在该位上出现的数量=0.1 * (X * Pow(10, Length(Z)+1)) + Pow(10, Length(Z))

 

    通过对上面3种情况的分析,我们可以看出,对于任意给定的整数n,我们都可以拆分成XYZ形式,并计算出Y位上'1'出现的数量;将Y从最高向最低位(个位)移动,可以计算出每位上'1'出现的数量,累计求和即为原题中要求的结果。Code如下:

 1//计算[1..number]中,共有多个字符c
 2private static Int64 Calc(Int64 number, char c)
 3ExpandedBlockStart.gifContractedBlock.gif{
 4    if (!Char.IsDigit(c) || c == '0')
 5        throw new Exception("暂只支持统计1..9出现的次数");
 6
 7    String s = number.ToString();
 8    Int32 digit = (Int32)c - 48;//将字符转换成数字(0对应的ASCII码值为48)
 9    Int64 totalCount = 0;
10
11    for (Int32 i = 0; i < s.Length; i++)//忽略掉刚加上的前缀0
12ExpandedSubBlockStart.gifContractedSubBlock.gif    {
13        Int32 num = (Int32)s[i] - 48;
14        switch (num.CompareTo(digit))
15ExpandedSubBlockStart.gifContractedSubBlock.gif        {
16            case -1://num<digit
17         String preStr = s.Substring(0, i);
18         totalCount += (Int64)(0.1 
19                   * Math.Pow(10, s.Length - i) 
20                          * (String.IsNullOrEmpty(preStr) ? 0 : Convert.ToInt64(preStr)));
21                break;
22            case 0//num=digit
23                String mantissa = s.Substring(i + 1);
24                totalCount += 1 + (String.IsNullOrEmpty(mantissa) ? 0 : Convert.ToInt64(mantissa));
25                goto case -1;
26           case 1//num>digit
27                totalCount += (Int64)Math.Pow(10, s.Length-i-1);
28                goto case -1;
29        }

30   }

31   return totalCount;
32}

33//调用
34Int64 count = Calc(number,'1');


    直接从数字n上就可以得出结果,时间复杂度O(1)。
    离开了人脑,“电脑”只是一坨废铁,别把啥东西都扔给它干,让废铁干它该干的活儿吧...

 

延伸思考:
1. 给出一个正整数n,求解1到n这些数对应的8进制中1出现的次数;
2. 给出一个正整数n,求解1到n这些数对应的16进制中A出现的次数;

 

转载于:https://www.cnblogs.com/happyhippy/archive/2008/10/19/1314708.html

浙江大学C语言上机练习题&答案 第2周(M2) 2 20011求华氏温度100°F对应的摄氏温度。 2 20012 求华氏温度 150°F 对应的摄氏温度。 3 20013求摄氏温度26°C对应的华氏温度。 3 20015当n为152时,分别求出n的个位(digit1)、十位(digit2)和百位(digit3)的值。 3 20026 输入2个整 num1 和 num2,计算并输出它们的和、差、积、商。 4 第3周(M3) 5 200311+2+3+......+100(调试示例error02_55 20032 求m+(m+1)+(m+2)+......+100 5 20033 求1/m+1/(m+1)1/(m+2)+......+1/n 6 20034 求1 + 1/3 + 1/5 + ......的前n项和 7 2003511/4+1/7-1/10+……的前n项之和 7 20036 输出华氏-摄氏温度转换表(改错题error02_6) 8 20038 求x的n次幂 9 20041 生成 3 的乘方表 10 20044 求100^0.5101^0.5+……+1000^0.5 10 20053 计算物体自由下落的距离 11 20056 计算分段函 11 20061 阶梯电价 12 20062 求m*m+1/m+(m+1)*(m+1)1/(m+1)(m+2)*(m+2)1/(m+2)+......+n*n+1/n 13 20063 求1-2/3+3/5-4/7+5/9-6/11+…… 14 20064 求2^1+2^2+2^3+……+2^n 15 第4周(M4) 15 10007 显示图案 (复习printf()的字符串输出) 15 20042 生成阶乘表 16 20043 使用函求 n! /(m!* (n-m)!) 16 20054 求平均值 17 20057 求11/2+1/3+......+1/n 18 20065 求0!+1!+2!+……+n! 18 40015 求最小值 19 40018 求a+aa+aaa+aa…a 20 第5周(M5) 21 30001 求一元二次方程的根 21 30002 求分段函的值 23 30003 分类统计字符 23 30004 显示五级记分制成绩所对应的百分制成绩区间(使用switch) 24 30005 显示水果的价格(使用switch) 25 30007 求三角形的面积和周长 27 30008 计算个人所得税 28 30051 判断闰年 29 30052 统计学生平均成绩及格人 30 30053 分段计算水费(使用嵌套的if-else语句) 31 第6周(M6) 32 40011 求最小公倍和最大公约(调试示例error04_1) 32 40012 求11/4+1/7-1/10+1/13-1/16+…… 33 40014 求整的位 34 40023 换硬币 35 40024 找出各位字的立方和等于它本身的 36 40025 找完(改错题error04_2) 38 40027 从高位开始逐位输出一个的各位(选作) 39 40052 判断素 40 40053 逆序输出整 41 40054 输出斐波那契序列 42 第7周(M7) 42 50002 使用函判断的符号 42 50003 使用函求奇和 43 50005 使用函统计素并求和 44 50006 使用函统计一个字的个 45 50007 使用函找水仙花 46 50009 使用函求余弦函的近似值 48 50052 使用函找最大值 49 50062 使用函输出指定范围内的 Fibonacci 50 50063 使用函找出指定范围内的完 51 第8周(M8) 52 40013 求奇52 40062 求x+x*x/2!+x*x*x/3!+x*x*x*x/4!+……的值 53 50004 使用函计算两点间的距离 54 50061 使用函求a+aa+aaa+aa…a 55 60002 整的十进制、八进制和十六进制表现形式 56 60003 分类统计字符 57 60006 验证歌德巴赫猜想 58 60007 使用函输出整的逆序 59 60009 统计单词 60 60062 简单计算器 611周 2 70011 简化的插入排序 2 70012 求平均值 5 70013 将组中的逆序存放 6 70014 求最大值及其下标 7 70015 交换最小值和最大值 8 70016 选择法排序 9 70017 在组中查找指定的元素 10 70021 求矩阵各行元素之和 11 70022 矩阵运算 12 70023 九九乘法表 13 夏2周 14 70024 判断上三角矩阵 14 70025 算算看,这是第几天? 15 70026 找鞍点(选作) 16 70031 将字符串逆序存放 17 70032 查找字符 18 70033 统计大写辅音字母 19 70034 字符串替换 20 70035 将十六进制字符串转换为十进制整 21 70036 将十进制字符串转换为十进制整 22 70052 统计字符出现次数 23 夏3周 24 10008 求1~100中能被6整除的所有整的和 24 20014 计算三门课程的平均成绩 25 20016 计算x的平方 25 20021 计算分段函的值 25 20022 计算摄氏温度 26 70051 找最大值并交换 27 80011 循环移动 28 80012 在组中查找指定元素 29 80013 使用函的选择法排序 30 80014 报 32 夏4周 35 10012 函程序设计 35 10024 计算最长的字符串长度 36 10025 字符串的连接 37 40017 求2/1+3/2+5/3+8/5+... 38 80021 找最大的字符串 39 80022 找最长字符串 40 80023 使用函删除字符串中的字符 41 80024 使用函实现字符串复制 42 80025 判断回文字符串 43 80026 分类统计字符个 44 夏5周 45 10014 计算函P(n,x) 45 10016 十进制转换二进制 46 10017 递归函程序设计求Fabonacci列 48 10019 改错题error10_1.cpp 49 10022 编程题 50 10026 指定位置输出字符串 50 10027 藏尾诗 51 10028 改错题error11_2.cpp 52 40065 分解质因 53 40067 打印图案 54 夏6周 56 30062 输出21世纪所有闰年 56 90001 调试示例error09_1.cpp 56 90002 时间换算 57 90003 计算平均成绩 58 90004 计算两个复之积 59 90005 查找书籍 60 90006 通讯录排序 61 90007 算算看,这是第几天? 62 90008 使用函实现时间换算 63 90009 找出总分最高的学生 64 其它练习 65 20027计算旅途时间。 65 20028字加密 66 教材习题3-4 (上机练习30009,统计学生成绩) 66 30061 出租车计费 67 教材习题4-12(p77) 68 教材习题4-14(p77) 69 50051 字金字塔(此题40067打印图案的思路相似) 69
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值