统计数字问题

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

分析与解答:

由0,1,2……9组成的所有的n位数,从n个0到n个9共有10^n个n位数,其中全排列的情况下,每个数字使用的次数一样,设为f(n).

clip_image002_thumb[2]

我们其实可以知道:f(n)=n10n-1

据此,可以从高向低位进行统计,再减去多余的0的个数即可。

本例程序,参考http://www.cnblogs.com/phinecos/archive/2008/12/14/1354780.html 但有多处开进。

#include <iostream> 
#include <string> 
#include <sstream> 
#include <cmath> 
#include <vector> 
#include <fstream>
using namespace std; 

double string_to_num(string str) 
{//字符串转换为double 
	double back; 
	istringstream instr(str); 
	instr>>back; 
	return back; 
} 
int main() 
{ 
	int len; 
	double mm;  //数字的个数
	int countNum[10] = {0};//保存统计结果
	vector<int> posNum; 
	string pageNum; 
	cout<<"input:";
	cin>>pageNum; 
	double num = string_to_num(pageNum); 
	////写入文件
	//ofstream outfil("C:\\Users\\xxx\\Desktop\\input.txt");
	//outfil<<num;

	len = pageNum.size(); 
	if( len <= 1 ) 
	{    //如果只有个位数,直接放到数组输出
		for(int i = 1; i <= int(num); i++)
			countNum[i] += 1; 
	} 
	else
	{ 
		const char* p = pageNum.c_str(); 
		for(int i = 0; i < len; i++) 
		{ 
			int curPosNum = int( *(p+i) ) - 48;//当前处理位置上的数字,从高位到低位
			//书上的公式为全数组的形式,即000-999
			//所以此公式适用于当前位为[0,curPosNum)时,其前面所有位的全形式。
			//应该当前位数减一,由书上公式得f(n-1)=(n-1)pow(10,n-2)
			mm = pow(10.0,len-i-2) * (len-i-1); //当个位的时候,位数n=len-i=1,mm为0
			if( mm != 0) 
			{//判断是不是个位
				//对低于当前位的位置先统计每个数的个数,当前位留到下一次
				for(int j = 0; j < curPosNum; j++)  //小于这个数的每个数的低位置的数的个数
				{ 
					for(int k = 0; k < 10; k++) 
						countNum[k] += mm; 
					countNum[j] += pow(10.0,len-i-1);  //加入当前位的个数
				} 
				if( !posNum.empty() ) 
				{ //判断是否为空
					for(int m = 0; m < posNum.size(); m++) 
					{ //当不为空的时候,计算每个容器vector中前个元素(即高位)应该增加的
					  //个数(在当前数字为curPosNum时),不包括全为0的情况(因为只有一个
					  //全为0,不能加多次,留在后面处理)
					  //核心:(出来一个低位,就把高位+相应个数)
						int tt = posNum.at(m); //安全防止溢出
						countNum[tt] += ( pow(10.0,len-i-1)*curPosNum ); 
					}        
				}    
				posNum.push_back(curPosNum);
				if( i == 0) //第0位
				{
					//去掉多余的0
					for(int j = 0; j < len; j++) 
						countNum[0] -= pow(10.0,len-j-1) ; //我得方法,从高位开始
					//如:0xx,0个数+10^2;再看0x,0个数+10^1;x,0个数+10^0
				}                        
			} 
			else
			{//指针位置来到个位上了,我的方法
				for(int n=0;n<=curPosNum;n++) 
				{//首先个位数上的数[0,curPosNum]每个数+1
					countNum[n]+=1;
				}
				if( !posNum.empty() )
				{ 
					for(int m = 0; m < posNum.size(); m++) 
					{  
						//其次,再考虑个位时,每个前面的高位对应的数都要+相应的个位数值的个数
						int tt = posNum.at(m); 
						countNum[tt] += ( pow(10.0,len-i-1)*curPosNum )+1 ; //+1就是为了处理全0的情况
					}        
				}    
			} 
		} 
	} 
	//cout<<"output:"<<endl;
	//ofstream outfile("C:\\Users\\xxx\\Desktop\\output.txt");
	//for (int i=0; i<10; i++)
	//{
	//	outfile<<countNum[i]<<endl;
	//}
	for(int i = 0; i < 10; i++) 
	{//输出个数
		cout<<countNum[i]<<endl; 
	}
	return 0; 
}

上面程序中,

at函数

语法:
TYPE at( size_type loc );

at() 函数 返回当前Vector指定位置loc的元素的引用. at() 函数 比 [] 运算符更加安全, 因为它不会让你去访问到Vector内越界的元素. 例如, 考虑下面的代码:

vector<int> v( 5, 1 );

for( int i = 0; i < 10; i++ ) {
  cout << "Element " << i << " is " << v[i] << endl;
}
这段代码访问了vector末尾以后的元素,这将可能导致很危险的结果.以下的代码将更加安全:
vector<int> v( 5, 1 );

for( int i = 0; i < 10; i++ ) {
  cout << "Element " << i << " is " << v.at(i) << endl;
}

取代试图访问内存里非法值的作法,at() 函数能够辨别出访问是否越界并在越界的时候抛出一个out_of_range异常.

程序网上还找到一种方法,虽然好理解,但是,不是很优化,算法复杂度不高,因为按照数逐个运算,随着数的增大。。。作为对比:

//这个题目有个最容易想到的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]); 

} 

}
posted @ 2012-03-23 18:31  csqlwy  阅读(3206)  评论(0编辑  收藏  举报