题目描述
输入
输出
样例输入
11
样例输出
1
4
1
1
1
1
1
1
1
1
方法:数学归纳法找出规律。
① 8
| 涉及数字 | 出现次数 |
个位 | 1-8 | 1 |
② 27
| 涉及数字 | 出现次数 |
个位 | 0-9 | 2 |
个位 | 1-7 | 1 |
十位 | 1 | 10 |
十位 | 2 | 8 |
③ 462
| 涉及数字 | 出现次数 |
个位 | 0-9 | 46 |
个位 | 1-2 | 1 |
十位 | 0-9 | 40 |
十位 | 1-6 | 10 |
百位 | 1-3 | 100 |
百位 | 4 | 63 |
④ 7891
| 涉及数字 | 出现次数 |
个位 | 0-9 | 789 |
个位 | 1-2 | 1 |
十位 | 0-9 | 780 |
十位 | 1-8 | 10 |
十位 | 9 | 2 |
百位 | 0-9 | 700 |
百位 | 1-7 | 100 |
百位 | 8 | 92 |
千位 | 1-6 | 1000 |
千位 | 7 | 892 |
核心思路:
将数字的每一位的计数分解成:高位对低位的影响计数和自身数值的影响计数。
其中,高位对低位影响表现形式是:低位上0-9数字组的出现次数;
低位自身的影响技术是:对高位取低位的模,得到的余数加一。
一.例子分析
抽象数字为···xxAxx···,探究数字A所在的位数和其数值的联系。
1. 当数字A是最低位时,其出现次数与高位的数字有关。
如: 页码462中的2,其共出现
46次完整的0-9集体计数加一(num/10)
1次0-2上的计数加一
2. 当数字A是中间位时,其出现次数与高位及低位有关。
如:页码462中的6,其共出现
40次完整的0-9集体计数加一((num/100)*10
10次0-5上的计数加一
3次数字6的计数加一
3. 当数字A是最高位时,其出现次数与自身和低位有关。
如:页码462中的4,其共出现
100次1-3的计数加一
63次数字4的计数加一
二.发现规律:
将0-9视作一组数字单元,表示对0-9每个数字都计数加一。记为符号X。
设此时数字为m,位数n。
1. 对非最高位:
X 出现次数:(num%pow(10,n+1))*pow(10,n)
小于m的1-9中的数字出现次数:pow(10,n)
等于m的数字出现次数:num%pow(10,n)+1
2. 对最高位:
小于m的1-9中的数字出现次数:pow(10,n)
等于m的数字出现次数:num%pow(10,n)+1
其中pow(a,b)表示数字a的b次方。
三.对0的探究
1. 取模加一是为了得到余数并且考虑个位出现一次0的情况。
2. 页码是从1开始计数,且小数字前面不加0。
如:372页中,97页数字是97,而非097.
因此,对非最高位来说:操作
X 出现次数:(num%pow(10,n+1))*pow(10,n)
已经将最高位为1、本位数字为0的情况考虑进去了,因此,需要在取模操作时将本位数字为0的情况减去。因此,对非最高位且本位数字小于m的数字的计数操作,都是从数字1开始计数,不再对0进行计数。
import java.util.Arrays;
import java.util.Scanner;
public class PageCount {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int num = sc.nextInt();
sc.close();
int[] count = solve(num);
for (int i = 0; i <= 9; i++) {
System.out.println(count[i]);
}
}
public static int[] solve(int num) {
int[] count = new int[10];
Arrays.fill(count, 0);
//value数组用于存储数字的每一位,由低位到高位存储。(123——>3,2,1)
int[] value = trans(num);
//max是给定数字的最高位
int max = value.length;
// 遍历数字的每一位
for (int i = 0; i < max; i++) {
// 对小于每一位上的数字值的数字进行计数
for (int j = 1; j <= value[i]; j++) {
// 对小于当前位的数字值计数次数为:10的当前位数的次方
if (j < value[i])
count[j] += Math.pow(10, i);
// 对当前位的数字值对应的数字计数为:num对10的i次方求模,再加一
else
count[j] += (int) (num % Math.pow(10, i)) + 1;
}
}
// 遍历数字的每一位,最高位不遍历
for (int i = 0; i < max - 1; i++) {
// 遍历0-9数字
for (int j = 0; j <= 9; j++) {
// 对count[]数组每一个元素进行计数
count[j] += ((int) (num / Math.pow(10, i + 1)) * Math.pow(10, i));
}
}
return count;
}
//trans函数将给定数字转换为int数组保存,且由低位到高位排列(数字123转换为数组value[],且值依次为3,2,1)
public static int[] trans(int num) {
String str = String.valueOf(num);
char[] array = str.toCharArray();
int[] value = new int[array.length];
for (int i = 0; i < array.length; i++) {
value[i] = (int) array[array.length - 1 - i] - (int) ('0');
}
return value;
}
}
补充:
顺便贴上 C的暴力实现:
#include <iostream>
#include <cstring>
using namespace std;
int main(){
long n,f[10],i,t;
memset(f,0,sizeof(f));
while(cin >> n){
for(i=1;i<=n;i++){
t=i;
while(t){
f[t%10]++;
t=t/10;
}
}
for(i=0;i<=9;i++){
cout << f[i] << endl;
}
}
return 0;
}
C++分块实现
#include <iostream>
#include <cstring>
using namespace std;
long f[10],i,l,h;
char s[10];
long a[10] = {0, 1, 20, 300, 4000, 50000, 600000, 7000000, 80000000, 900000000};
long b[10] = {0, 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000};
void compute(int n){
sprintf(s,"%d",n);
l = strlen (s);
if(l==1)
{
for(i=1;i<=n;i++)
f[i]=f[i]+1;
return;
}
else
{
h=s[0]-48;
for(i=0;i<=9;i++){
f[i]=f[i]+a[l-1];
}
f[0]=f[0]-b[l-1];
for(i=0;i<=9;i++){
f[i]=f[i]+(h-1)*a[l-1];
}
for(i=1;i<h;i++){
f[i]=f[i]+b[l];
}
f[h]=f[h]+n-h*b[l]+1;
n=n-h*b[l];
f[0]=f[0]+b[l-1];
compute(n);
}
}
int main()
{
int n;
cin >> n;
compute(n);
for(i=0;i<=9;i++)
{
cout << f[i] << endl;
}
return 0;
}