計數問題——統計給定頁碼之前0-9數字出現次數(java語言實現)
題目描述
一本書的頁碼從自然數1開始順序編碼直到自然數n。書的頁碼按照通常的習慣編排,每個頁碼都不含多餘的前導數字0。例如,第6頁用數字6表示,而不是06或006等。數字計數問題要求對給定書的總頁碼n,計算出書的全部頁碼中分別用到多少次數字0,1,2,...,9。輸入
只有1行,表示書的總頁碼的整數n。輸出
共有10行,在第k行輸出頁碼中用到數字k-1的次數,k=1,2,...10。樣例輸入
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;
}