【编程马拉松算法目录>>>】
【004-包含一】【工程下载>>>】
1 题目描述
NowCoder总是力争上游,凡事都要拿第一,所以他对“1”这个数情有独钟。爱屋及乌,他也很喜欢包含1的数,例如10、11、12……。你能帮他统计一下有多少个包含1的正整数吗?
1.1 输入描述:
输入有多组数据,每组数据包含一个正整数n,(1≤n≤2147483647)。
1.2 输出描述:
对应每组输入,输出从1到n(包含1和n)之间包含数字1的正整数的个数。
1.3 输入例子:
1
9
10
20
1.4 输出例子:
1
1
2
11
2 解题思路
假设有数字
X(n)=xnxn−1…x2x1x0
,
xi
上的权重是
10i
。
先考虑
0~9n9n−1…929190
,中出现1的数字个数,假设它是
P(n)
,它由三部分组成:
-
0~9n−1…929190
,含有1的数字数目是
P(n−1)
-
1n0n−1…929190~1n9n−1…929190
,含有1的数字数目是
10n−1
-
1<ji<xi
,
ji0i−1…020100 ji9i−1…929190
中含有1的数字数目是
P(n−1)
,
ji
可以取8个数字。
所以
P(n−1)=P(n−1)+10n−1+8∗P(n−1)=9∗P(n−1)+10n−1
,又
n=0
时,
P(n)=1
,综上有:
P(n)={19∗P(n−1)+10n−1n=0n>0
再考虑
X(n)
,从右到左处理
X(n)
上的每一位,假设当前处理第i位。则要分三种情况:
第一种:
xi=0
,则
xixi−1…x2x1x0
与
xi−1…x2x1x0
含有1的数字个数相同,则
F(i)=F(i−1)
。
第二种:
xi=1
,则
xixi−1…x2x1x0
包含1的由两部分组成:
-
0~9i−1…929190
中含有1的数字数,为
P(i−1)
-
xi0i−1…020100~xixi−1…x2x1x0
中含有1的数字,为
X(i−1)+1
则有
F(i)=P(i−1)+X(i−1)+1
第三种:
xi>1
,则
xixi−1…x2x1x0
包含1的由四部分组成:
-
0~xi−1…x2x1x0
中含有1的数字数,为
P(i−1)
-
1i0i−1…020100~1i9i−1…929190
中含有1的数字数,为
10i−1
-
1<ji<xi
,
ji0i−1…020100~ji9i−1…929190
中含有1的数字数,为
P(i−1)
。j_i可以取
xi−2
个
-
xi0i−1…020100 xixi−1…x2x1x0
中含有1的数字,为
F(i−1)
则有
F(i−1)=P(i−1)+10i−1+(xi−2)P(i−1)+F(i−1)=(xi−1)P(i−1)+10i−1+F(i−1)
综合有:
当
n=0
时,
X(n)=x0
F(n)=1
当 n>0 时, X(n)=xnxn−1…x2x1x0
F(n)=⎧⎩⎨F(n−1)P(n−1)+X(n−1)+1(xn−1)P(n−1)+10n−1+F(n−1)xn=0xn=1xn>1
3 算法实现
import java.util.Scanner;
/**
* Author: 王俊超
* Time: 2016-05-06 17:52
* CSDN: http://blog.csdn.net/derrantcm
* Github: https://github.com/Wang-Jun-Chao
* Declaration: All Rights Reserved !!!
*/
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Scanner scanner = new Scanner(Main.class.getClassLoader().getResourceAsStream("data.txt"));
while (scanner.hasNext()) {
int n = scanner.nextInt();
System.out.println(countOne(n));
}
scanner.close();
}
/**
* 【方法一】
* 计算[1-n]中包含数字1的数字个数
*
* @param n 最在范围
* @return 包含数字1的数字个数
*/
private static int countOne(int n) {
int countedN = 0;
int result = 0;
// 从右向左分析n的每一位;for循环中:i 表示分析到了哪一位,i=1表示个位,i=10表示十位,以此类推;
// onesPerI 表示从0到i-1含有1的数的个数,0,1,19 ...;
// cur 是目前分析的那一位的数值;
// 举个例子: f(m,n) 表示从m到n,含有1的数字的个数。
// f(1,500) = f(1, 99)+f(100, 199)+f(200, 299)+(300, 399)+f(400, 499)
// f(1, 99) = f(200, 299) = f(300, 399) = f(400, 499)
// f(100, 199) = 100
for (int i = 1, onesPerI = 0, cur; n != 0; onesPerI = onesPerI * 9 + i, i *= 10, n /= 10) {
// 当前数位的数值
cur = n % 10;
if (cur == 0) {
continue;
} else if (cur == 1) {
// onesPerI表示[1, i-1]含有1的个数,countedN表示比
result = onesPerI + countedN + 1;
} else {
result += (cur - 1) * onesPerI + i;
}
// 表示比第i位以及比第i位低的各位的数值,比如abcdef,现在处理万位,那么countN就是bcdef
countedN += cur * i;
}
return result;
}
}
4 测试结果
5 其它信息
因为markddow不好编辑,因此将文档的图片上传以供阅读。Pdf和Word文档可以在Github上进行【下载>>>】。