算法实现题---统计数字

问题描述:

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

编程任务

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

数据输入:

        输入数据由文件名为 input.txt 的文本文件提供。 每个文件只有 1 行,给出表示书的总页码的整数 n。

结果输出:

        程序运行结束时,将计算结果输出到文件 output.txt 中。输出文件共有 10 行,在第 k 行 输出页码中用到数字 k-1 的次数,k=1,2,…10。

思考:

这道题很容易想到暴力解法,比如把各个数进行取余运算再进行数字匹配,或者把所有的数字组成字符串再进行遍历和匹配计数,但这样的作法只能应付数据较小的情况,题目的数据上限是10^9,所以这些方法都不适宜。
查阅一些资料后了解到这样一个公式:
n位数的各个数字出现的次数满足:n*10^(n-1)。
这里的“n位数”是指用0补位的,例如三位数是值 000 - 999。

我们可以把一个n+1位数拆分为一个首位数(m,m肯定不是0) 和一个 n位数(k)。这个k定然满足上述 "n位数" 的情况。
例如:给定页数1315,我们可以把它拆分为 首位8和一个3位数。

在有首位数(m)的前提下,上面的公式就发生了一个小的变化,这个很好理解,假设我们给定数据3556,m=3,共有如下3种有效情况:

1.首位数为1,1连接3位数。
2.首位数为2,2连接3位数。
3.首位数为3,3连接3位数。


那么上面的公式就变成这样:一个n+1位数,在去除首位数(m)的情况下,后面n位数出现0-9的次数均为 m*n*10^(n-1)。
到这里我们已经完成了第一步,然后再考虑首位数的情况,首位数出现的次数与后面的数有关,这个很好理解。
至此,一个n+1位数的情况已经分析得差不多了。我们还得考虑一些特殊情况,比如1000,1003这些数,我们对数去首位的方法采用取余法,这样会过滤掉中间或者尾部的0,针对这类情况我们直接将其加上就行。
下面根据分析情况得出代码:
/*
统计数字问题
*/

#include <string>
#include <iostream>
#include <fstream>
#include <ostream>
#include <stdlib.h>
#include <vector>
#include <ctime>
#include <cmath>
using namespace std;

void func(unsigned long num, int size, vector<unsigned long> & count)
{
	int first_num = num / int(pow(10, size - 1));						//首位数
	unsigned long after_fst_num = num % int(pow(10, size - 1));			//去除首位数得余数
	int size_after_fst_num = to_string(after_fst_num).size();			//余数位数											//余数位数
	
	//去除首位,补0计数
	for (int i=0; i<10; i++)
	{
		count[i] += unsigned long(first_num * (size - 1) * pow(10, size - 2));
	}

	//首位从0到first_num计数
	count[first_num] += after_fst_num + 1;		// +1是因为多了个了全零位
	for (int i = 0; i < first_num; i++)
	{
		count[i] += unsigned long(pow(10, size - 1));
	}

	if (after_fst_num == 0)		
	{
		if (size - 1 > 0)
			count[0] += size - 1;	//加上尾巴的0
		return;
	}
	else if (size - size_after_fst_num > 1)		//加上中间的0,因为每次递归都是用的余数,中级的0会被跳过
	{
		count[0] += unsigned long( (size - size_after_fst_num)*after_fst_num );
	}

	func(after_fst_num, size_after_fst_num, count);
}

int main()
{
	//time_t t1 = clock();		//测试运行时间
	//读取测试文件数据--页数
	string _c;	//存放页数
	fstream f("input.txt");
	getline(f, _c);
	f.close();
	int size = _c.size();								//size : 输入数据的位数
	unsigned long  num = atoi(_c.c_str());				//num  : 测试数据


	vector<unsigned long> count;
	for (int i = 0; i < 10; i++)
	{
		count.push_back(0);	//初始化数组
	}

	func(num, size, count);
	//减去前置多填充的0,位数从1开始
	for (int i = 1; i <= size; i++)
	{
		count[0] -= unsigned long(pow(10, i - 1));
	}

	//输出到屏幕
	for (auto x : count)
	{
		cout << x << endl;
	}

	//输出到文件
	ofstream outfile;
	outfile.open("outfile.txt");
	for (auto x : count)
	{
		outfile << x << endl;
	}
	outfile.close();

	//记录测试时间
	//time_t t2 = clock();
	//cout << "运行时间: " << t2 - t1 << " ms" << endl;

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值