数字统计问题


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

普通方法

从1遍历到n,依次,依次对每个数辗转相除取余,方法比较简单,直接贴代码:

//普通方法
public static int[] countNum1(int n) {
		int[] num = new int[10];
		for(int i=1;i<=n;i++) {
			int t = i;
			while(t!=0) {
				int c = t%10;
				t /= 10;
				num[c]++;
			}
		}
		return num;
	}

按位统计

对于一个4位数要想统计1出现的次数,分别统计1在个位,十位,百位,千位,万位出现的次数再相加就可以得到1出现的总次数;

		80236
		eg:
		8023 6	
		cur=6,before=8023,after=0,k=1;
		个位循环了8023次,循环一次,0-9在个位上出现1次;
		第8024次只循环到6,0-6再出现1次

		802 3 6
		cur=3,before=802,after=6,k=10;
		十位循环了802次,循环一次,0-9在十位上出现10次;
		第803次只循环到36,0-2在十位上再出现10次,3出现(6+1)次

		80 2 36
		cur=2,before=80,after=36,k=100;
		百位循环了80次,循环一次,0-9在百位上出现100次;
		第81次只循环到236,0-1在百位上再出现100次,2出现(36+1)次;
		
		i表示要统计的数字
		          (before+1) * k		         i<cur	
		f(i)=     before*k+after+1               i=cur   0<i<=9	
		          before*k		                 i>cur



		再分析0:
		802 3 6
		cur=3,before=802,after=6,k=10;
		十位循环了802+1次,循环一次,0在十位上出现10次;(0,9)表示0-9其中的一个数
		第一次循环:before=000    cur=0    Num=0000+(0,9)   
		第二次循环:before=001    cur=0    Num=0010+(0,9)
		第三次循环:before=002    cur=0    Num=0020+(0,9)
		...
		第862次循环:before=801   cur=0    Num=8010+(0,9)
		第863次循环:before=802   cur=0    Num=8020+(0,9)
		由此可看出,统计0的时候,多循环了一次,即第一次循环before=000时 
		f(0) = (before+1-1)*k;  cur>0
		
		再分析0:
		8 0 236
		cur=0,before=8,after=236,k=1000;(0,999)表示0-999其中的一个数
		千位循环了8次,循环一次,0在十位上出现1000次;
		第一次循环:before=0    cur=0    Num=00+(0,999)
		第二次循环:before=1    cur=0    Num=10+(0,999)
		第三次循环:before=2    cur=0    Num=20+(0,999)
		...
		第8次循环: before=7   cur=0    Num=70+(0,999)
		第9次循环: before=8   cur=0    Num=80+(0,236)
		多循环一次,去掉第一次循环;最后一次循环只循环到236
		cur=i=0;
		f(0) = (before+1-1-1)*k+(after+1)=(before-1)*k



		因此:
		
		i表示要统计的数字
		        (before+1) * k		     i<cur	
		f(i)=   before*k+after+1         i=cur    0<i<=9	
		        before*k		         i>cur

			    before*k                 i<cur
		f(0)=                                       i=0
			    (before-1)*k             i=cur  

按位统计代码

//按位统计
public static int[] countNum(int n) {
		int[] num = new int[10];
		int k = 1;
		while (k <= n) {
			int after = n % k;  
			int before = n / k / 10;
			int cur = n / k % 10;
			if(cur!=0)
				num[0] +=  before * k;
			if(cur==0)
				num[0] += (before-1) * k +after+1;
			for (int i = 1; i < 10; i++) {
				if (i > cur)
					num[i] += before * k;
				else if (i == cur)
					num[i] += (after + before * k+1);
				else if (i < cur)
					num[i] += (before + 1) * k;

			}
			k *= 10;
		}
		return num;
	}

两种方法对比

普通方法时间复杂度为O(n),
按位统计时间复杂度为O(lgn)
测试:
n=1亿


public class Main {
	public static void main(String[] args) {
		long l1 = System.currentTimeMillis();
		int[] num = countNum(100000000);
		long l2 = System.currentTimeMillis();
		System.out.println("按位统计方法时间:"+(l2-l1)+"ms");
		int[] num2 = countNum1(100000000);
		long l3 = System.currentTimeMillis();
		System.out.println("普通方法时间:"+(l3-l2)+"ms");
		
	}
	//普通方法
	public static int[] countNum1(int n) {
		int[] num = new int[10];
		for(int i=1;i<=n;i++) {
			int t = i;
			while(t!=0) {
				int c = t%10;
				t /= 10;
				num[c]++;
			}
		}
		return num;
	}
	//按位统计
	public static int[] countNum(int n) {
		int[] num = new int[10];
		int k = 1;
		while (k <= n) {
			int after = n % k;  
			int before = n / k / 10;
			int cur = n / k % 10;
			if(cur!=0)
				num[0] +=  before * k;
			if(cur==0)
				num[0] += (before-1) * k +after+1;
			for (int i = 1; i < 10; i++) {
				if (i > cur)
					num[i] += before * k;
				else if (i == cur)
					num[i] += (after + before * k+1);
				else if (i < cur)
					num[i] += (before + 1) * k;

			}
			k *= 10;
		}
		return num;
	}

}

输出:
按位统计方法时间:0ms
普通方法时间:2922ms

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值