关于统计数字问题的算法

一本书的页码从自然数1开始顺序编码直到自然数n。书的页码按照通常的习惯编排,每个页码都不含多余的前导数字0。例如第6页用6表示而不是06或006。数字统计问题要求对给定书的总页码,计算出书的全部页码中分别用到多少次数字0,1,2,3,.....9。
这个题目有个最容易想到的n*log10(n)的算法。这是自己写的复杂度为O(n*log10(n))的代码:

void statNumber(int n) {
  int i, t;
  int count[10] = {0};
  for(i = 1; i <= n; i++) {
  t = i;
  while(t) {
    count[t%10]++;
    t/=10;
  }
  }
  for(i = 0; i < 10; i++) {
  printf("%d/n", count[i]);
  }
}

仔细考虑m个n位十进制数的特点,在一个n位十进制数的由低到高的第i个数位上,总是连续出现10^i个0,然后是10^i个1……一直到10^i个9,9之后又是连续的10^i个0,这样循环出现。找到这个规律,就可以在常数时间内算出第i个数位上每个数字出现的次数。而在第i个数位上,最前面的10^i个0是前导0,应该把它们减掉。

这样,可以只分析给定的输入整数n的每个数位,从面可以得到一个log10(n)的算法,代码如下:

void statNumber(int n) {
  int m, i, j, k, t, x, len = log10(n);
  char d[16];
  int pow10[12] = {1}, count[10] = {0};
  for(i = 1; i < 12; i++) {
  pow10[i] = pow10[i-1] * 10;
  }
  sprintf(d, "%d", n);
  m = n+1;
  for(i = 0; i <= len; i++) {
  x = d[i] - '0';
  t = (m-1) / pow10[len-i]; 

  count[x] += m - t * pow10[len-i]; 

  t /= 10;
  j = 0;
  while(j <= x-1) {
    count[j] += (t + 1) * pow10[len-i];
    j++;
  }
  while(j < 10) {
    count[j] += t * pow10[len - i];
    j++;
  }
  count[0] -= pow10[len-i]; /* 第i个数位上前10^i个0是无意义的 */
  }
  for(j = 0; j < 10; j++) {
  printf("%d/n", count[j]);
  }
}

通过对随机生成的测试数据的比较,可以验证第二段代码是正确的。
对两段代码做效率测试,第一次随机产生20万个整数,结果在我的电脑上,第二段代码执行1.744秒。第一段代码等我吃完钣回来看还是没反应,就强行关了它。
第二次产生了1000个整数,再次测试,结果第一段代码在我的电脑上执行的时间是
10.1440秒,而第二段代码的执行时间是0.0800秒。

其原因是第一段代码时间复杂度为O(n*log10(n)),对m个输入整数进行计算,则需要的时间为 1*log10(1) + 2*log10(2) + ... + m*log10(m), 当n > 10时,有
n*log10(n) > n,所以上式的下界为11+12+....+m,其渐近界为m*m。对于20万个测试数据,其运行时间的下界就是4*10^10。
同样可得第二段代码对于n个输入数据的运行时间界是n*log10(n)的。

上面的代码中有个pow10数组用来记录10^i,但10^10左右就已经超过了2^32,但是题目给定的输入整数的范围在10^9以内,所以没有影响。
原著中给出的分析如下:
考察由0,1,2...9组成的所有n位数。从n个0到n个9共有10^n个n位数。在这10^n个n位数中,0,1,2.....9第个数字使用次数相同,设为f(n)。f(n)满足如下递推式:
n>1:
f(n) = 10f(n-1)+10^(n-1)
n = 1:
f(n) =1
由此可知,f(n) = n*10^(n-1)。
据此,可从高位向低位进行统计,再减去多余的0的个数即可。
著者的思想说的更清楚些应该是这样:
对于一个m位整数,我们可以把0到n之间的n+1个整数从小到大这样来排列:
000......0
.............
199......9
200......0
299......9
.........
这样一直排到自然数n。对于从0到199......9这个区间来说,抛去最高位的数字不看,其低m-1位恰好
就是m-1个0到m-1个9共10^(m-1)个数。利用原著中的递推公式,在这个区间里,每个数字出现的次数
(不包括最高位数字)为(m-1)*10^(m-2)。假设n的最高位数字是x,那么在n之间上述所说的区间共有
x个。那么每个数字出现的次数x倍就可以统计完这些区间。再看最高位数字的情况,显然0到x-1这些
数字在最高位上再现的次数为10^(m-1),因为一个区间长度为10^(m-1)。而x在最高位上出现次数就是
n%10^(m-1)+1了。接下来对n%10^(m-1),即n去掉最高位后的那个数字再继续重复上面的方法。直到
个位,就可以完成题目要求了。
比如,对于一个数字34567,我们可以这样来计算从1到34567之间所有数字中每个数字出现的次数:
从0到9999,这个区间的每个数字的出现次数可以使用原著中给出的递推公式,即每个数字出现4000次。
从10000到19999,中间除去万位的1不算,又是一个从0000到9999的排列,这样的话,从0到34567之间
的这样的区间共有3个。所以从00000到29999之间除万位外每个数字出现次数为3*4000次。然后再统计
万位数字,每个区间长度为10000,所以0,1,2在万位上各出现10000次。而3则出现4567+1=4568次。
之后,抛掉万位数字,对于4567,再使用上面的方法计算,一直计算到个位即可。
下面是自己的实现代码:

void statNumber_iterative(int n) {
  int len, i, k, h, m;
  int count[10] = {0};
  int pow10[12] = {1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000};
  char d[16];
  len = log10(n);   /* len表示当前数字的位权 */
  m = len;
  sprintf(d, "%d", n);
  k = 0;     /* k记录当前最高位数字在d数组中的下标 */
  h = d[k] - '0';   /* h表示当前最高位的数字 */
  n %= pow10[len];    /* 去掉n的最高位 */
  while(len > 0) {
  if(h == 0) {
    count[0] += n + 1;
    h = d[++k] - '0';
    --len;
    n %= pow10[len];
    continue;
  }
  for(i = 0; i < 10; i++) {
    count[i] += h * len * pow10[len-1];
  }
  for(i = 0; i < h; i++) {
    count[i] += pow10[len];
  }
  count[h] += n + 1;
  --len;
  h = d[++k] - '0';
  n %= pow10[len];
  }
  for(i = 0; i <= h; i++) {
  count[i] += 1;
  }
  /* 减去前导0的个数 */
  for(i = 0; i <= m; i++) {
  count[0] -= pow10[i];
  }
  for(i = 0; i < 10; i++) {
  printf("%d/n", count[i]);
  }
}

以上就是本文的全部内容,希望对大家的学习有所帮助。

(0)

相关推荐

  • php简单统计字符串单词数量的方法

    本文实例讲述了php简单统计字符串单词数量的方法.分享给大家供大家参考.具体实现方法如下: <?php function word_count($sentence){ $array = explode(" ", $sentence); return count($array); } $words = word_count("The is a group of words"); echo $words; ?> 希望本文所述对大家的php程序设计有所帮助.

  • PHP统计数值数组中出现频率最多的10个数字的方法

    本文实例讲述了PHP统计数值数组中出现频率最多的10个数字的方法.分享给大家供大家参考.具体分析如下: 该问题属于TOPK范畴,统计单词出现频率,做报表,数据统计的时会常用! php代码如下: //随机生成数值数组 for($i=0;$i<1000;$i++){ $ary[]=rand(1,1000); } //统计数组中所有的值出现的次数 $ary=array_count_values($ary); arsort($ary);//倒序排序 $i=1; foreach($ary as $key=

  • C语言统计字符个数代码分享

    C语言实现统计字符个数 #include<stdio.h> int main() { int sz[10]={0},zm[26]={0},z[26]={0},i,space=0,e=0,t=0; char c; printf("请输入一段字符,统计其中各字符的数量\n"); while((c=getchar())!='\n') { if(c<='z'&&c>='a') zm[c-'a']++; else if(c<='Z'&&

  • C#统计字符串中数字个数的方法

    本文实例讲述了C#统计字符串中数字个数的方法.分享给大家供大家参考.具体实现方法如下: // DigitCounter.cs // 编译时使用:/target:library using System; // 声明与 Factorial.cs 中的命名空间相同的命名空间.这样仅允许将 // 类型添加到同一个命名空间中. namespace Functions { public class DigitCount { // NumberOfDigits 静态方法计算 // 传递的字符串中数字字符的数

  • 关于统计数字问题的算法

    一本书的页码从自然数1开始顺序编码直到自然数n.书的页码按照通常的习惯编排,每个页码都不含多余的前导数字0.例如第6页用6表示而不是06或006.数字统计问题要求对给定书的总页码,计算出书的全部页码中分别用到多少次数字0,1,2,3,.....9. 这个题目有个最容易想到的n*log10(n)的算法.这是自己写的复杂度为O(n*log10(n))的代码: void statNumber(int n) { int i, t; int count[10] = {0}; for(i = 1; i <=

  • C++数位DP复杂度统计数字问题示例详解

    目录 一.问题描述: 二.问题分析: 1. 抽取题意: 2. 初步思考: 3. 示例分析: 4. 总结规律: 5. 解除约定: 三. 编写代码: 四. 相关例题: Tips:如果你是真的不理解,不要只看,拿出笔来跟着步骤自己分析. 一.问题描述: 一本书的页码从自然数 1 开始顺序编码直到自然数 n .书的页码按照通常的习惯编排, 每个页码不含多余的前导数字 0. 例如, 第 6 页用数字 6 表示而不是 06 或 006等. 数字计数问题要求对给定书的总页码 n,计算书的全部页码分别用到多少次

  • SQL Server自动更新统计信息的基本算法

    自动更新统计信息的基本算法是: · 如果表格是在 tempdb 数据库表的基数是小于 6,自动更新到表的每个六个修改. · 如果表的基数是大于 6,但小于或等于 500,更新状态每 500 的修改. · 如果基数大于 500,表为更新统计信息时(500 + 20%的表)发生了更改. · 表变量为基数的更改不会触发自动更新统计信息. 注意:此严格意义上讲,SQL Server 计算基数为表中的行数. 注意:除了基数,该谓语的选择性也会影响 AutoStats 生成.这意味着该统计信息可能无法更新的

  • python计算书页码的统计数字问题实例

    本文实例讲述了python计算书页码的统计数字问题,是Python程序设计中一个比较典型的应用实例.分享给大家供大家参考.具体如下: 问题描述:对给定页码n,计算出全部页码中分别用到多少次数字0,1,2,3,4...,9 实例代码如下: def count_num1(page_num): num_zero = 0 num_one = 0 num_two = 0 num_three = 0 num_four = 0 num_five = 0 num_six = 0 num_seven = 0 nu

  • JavaScript实现的一个计算数字步数的算法分享

    这两天看了下某位大神的github,知道他对算法比较感兴趣,看了其中的一个计算数字的步数算法,感觉这个有点意思,所以就自己实现了一个. 算法描述与实现原理 给出一个整型数字,统计出有多少种走法可以到达目标,比如一个数字4,可以有下面几种走法 复制代码 代码如下: [ 1, 3 ]         [ 4 ]     [ 1, 1, 2 ]         [ 2, 2 ]     [ 1, 1, 1, 1 ] 其实通过上面的组合可以得出下面的结论. 1.先列出所有项是1的组合 2.依次从左到右项

  • python实现识别手写数字 python图像识别算法

    写在前面 这一段的内容可以说是最难的一部分之一了,因为是识别图像,所以涉及到的算法会相比之前的来说比较困难,所以我尽量会讲得清楚一点. 而且因为在编写的过程中,把前面的一些逻辑也修改了一些,将其变得更完善了,所以一切以本篇的为准.当然,如果想要直接看代码,代码全部放在我的GitHub中,所以这篇文章主要负责讲解,如需代码请自行前往GitHub. 本次大纲 上一次写到了数据库的建立,我们能够实时的将更新的训练图片存入CSV文件中.所以这次继续往下走,该轮到识别图片的内容了. 首先我们需要从文件夹中

  • php数字转汉字代码(算法)

    复制代码 代码如下: //将数字转换为汉字,比如1210转换为一千二百一十 $num = "842105580";//九位数 function del0($num) //去掉数字段前面的0 { return "".intval($num); } function n2c($x) //单个数字变汉字 { $arr_n = array("零","一","二","三","四"

  • Java面试题冲刺第二十三天--算法(2)

    目录 面试题1:你说一下常用的排序算法都有哪些? 追问1:谈一谈你对快排的理解吧 追问2:说一下快排的算法原理 追问3:来吧!给我手敲一个快排 面试题2:来!再给我手撸一个Spring 追问1:哦,咳咳-说一下构成递归的前提条件有啥? 追问2:递归都有哪些优缺点? 追问3:给我手写一个简单的递归算法的实现吧 面试题3: 10亿个数中找出最大的100000个数(top K问题) 总结 面试题1:你说一下常用的排序算法都有哪些? 追问1:谈一谈你对快排的理解吧 快速排序,顾名思义就是一种以效率快为特

  • jquery+php实现滚动的数字特效

    有时我们需要动态的展示访问次数.下载次数等效果,我们可以借助jQuery结合后台php实现一个滚动的数字展示效果. 本文以实时获取某产品的下载次数为场景,前台定时执行javascript获取最新的下载次数,并滚动更新页面上的下载次数. HTML 我们首先载入jQuery库文件和动画背景插件:animateBackground-plugin.js. <script type="text/javascript" src="js/jquery.js"><

  • 求解旋转数组的最小数字

    求解旋转数组的最小数字 题目描述: 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转.输入一个递增排序的数组的一个旋转,输出旋转数组的最小数组.例如数组{3,4,5,1,2}是数组{1,2,3,4,5}的旋转数组,该数组的最小值为1. 思路解析: O(N)的算法 这种算法的思想就是遍历这个数组,由于这个数组是两部分有序的数组,因此遍历这个数组时当后一个数字小于前一个数字时,则后一个(即较小)一定为整个数组中最小的数字. 这种算法的思想很简单,但就是时间复杂度较大,因此不是很好的算

随机推荐