1. 前言
本文的一些图片, 资料 截取自编程之美
2. 问题描述
3. 问题分析
对于第一个问题 书中给出了两种算法
第一种 : 遍历一次1-n 统计出每一个数的出现的1的次数, 然后累加起来
第二种 : 利用数学方法找出规律, 然后计算 [呵呵, 一看就知道经过仔细思考过的算法效率高的多]
现在以 113为例解释一下第二种算法
1-113 中1出现的次数 = 1-113中[个位为1的数字的个数 + 十位为1的数字的个数 + 百位为1的数字的个数]
所以 1-113 中1出现的次数为 : 12 + 14 + 14 = 40
个位为1 : [[0*100 + [0-9] * 10] + 1] / [1*100 + [0-1]*10 + 1] = 11 + 1
十位为1 : [0 * 100 + 1*10 + [0-9]] / [1*100 + 1*10 + [0-3]] = 10 + 4
百位为1 : [1*100 + 0*10 + [0-9]] + [1*100 + 1*10 + [0-3]] = 14
通过归纳法 我们可以得到如下结论 :
因为 1 - n 中1的个数 等于个位为1的数的个数 + 百位为1的数的个数 + … + 最高位为1的数的个数
令mask 为当前测试位数的权重, [high表示当前位之前的数字的值, low表示当前位之后的数字的值, [比如 : 123, 设当前位为第二位2, 那么high为1, low为3]]
则 以第2位为例 :
如果第二位为0 则1-n中1的个数为 high * mask
如果第二位为1 则1-n中1的个数为 high * mask + low + 1
如果第二位为2-9 则1-n中1的个数为(high + 1) * mask
对于第二个问题
书中给出的解法为, 求解f(n) == n的上界, 然后在从上界开始递减 寻找满足条件的数字, 对于这个思路, 我看了一下 也是一知半解
证明存在上界 :
求出上界 :
add at 2016.02.12
今天在伯乐在线上面又看见了一种比较巧妙的解法, 这里 我就直接上图了
附上帖子的location : http://group.jobbole.com/13802/
4. 代码
/**
* file name : Test11FindNumOf1In1ToN.java
* created at : 10:02:01 AM May 20, 2015
* created by 970655147
*/
package com.hx.test03;
public class Test11FindNumberOf1In1ToN {
// 找到1到n 中所有的 1的个数[10进制表示]
public static void main(String []args) {
int n = 113;
// 1602000 1604000 1606000 1608000
// findNumOf1In1ToN01(n);
findNumOf1In1ToN02(n);
// for(int i=0; i<999999999; i+=1000) {
// int num = findNumOf1In1ToN02(i);
// Log.log(i, (i > num) );
// if(i < num) {
// Tools.awaitInput();
// }
// }
for(int i=0; i<100; i++) {
int num = findNumOf1In1ToN02(i);
// Log.log(i, (i > num) );
Log.log(Integer.toBinaryString(num));
Tools.awaitInput();
}
}
// 从1遍历到n 统计共出现了多少个1
public static int findNumOf1In1ToN01(int n) {
int cnt = 0;
for(int i=1; i<=n; i++) {
cnt += numOf1InN(i);
}
Log.log(cnt);
return cnt;
}
// 统计n中出现了多少个1
private static int numOf1InN(int n) {
int cnt = 0;
while(n > 0) {
if(n % 10 == 1) {
cnt ++;
}
n /= 10;
}
return cnt;
}
// 思路 : 1 - n 中1的个数 等于个位为1的数的个数 + 百位为1的数的个数 + ... + 最高位为1的数的个数
// mask 为当前测试位数的权重
// 以第2位为例 : 如果第二位为0 则1-n中1的个数为 high * mask
// 如果第二位为1 则1-n中1的个数为 high * mask + low + 1
// 如果第二位为2-9 则1-n中1的个数为(high + 1) * mask
// 2103为例
// 第二位为0 十位为1的数的个数为高位[百位, 千位][0 - 20][21个] * 低位[个位][0 - 9][10个] = 21 * 10 = 210
// 2113为例
// 第二位为1 十位为1的数的个数为 (高位[百位, 千位][0 - 20][21个] * 低位[个位][0 - 9][10个]) + (高位为21[1个] * 低位为[0-3][4个]) = 22 * 10 + 3 + 1 = 224
// 2123为例
// 第二位为2 十位为1的数的个数为高位[百位, 千位][0 - 21][22个] * 低位[个位][0 - 9][10个] = 22 * 10 = 220
public static int findNumOf1In1ToN02(int n) {
int mask = 1, high = 0, low = 0, cur = 0;
int cnt = 0;
while(n >= mask) {
high = n / (mask * 10);
low = n % mask;
cur = (n / mask) % 10;
if(cur == 0) {
cnt += high * mask;
} else if(cur == 1) {
cnt += high * mask;
cnt += (low + 1);
} else {
cnt += (high + 1) * mask;
}
mask *= 10;
}
Log.log(cnt);
return cnt;
}
}
5. 运行结果
6. 总结
这里再一次的证明了, 算法和数学的紧密联系, …
注 : 因为作者的水平有限,必然可能出现一些bug, 所以请大家指出!