在www.blogjava.net上看到的有趣题目
http://www.blogjava.net/Jack2007/archive/2008/10/16/234742.html
原文作者是Jack.Wang
题目描述:
给定一个十进制数N,写下从1开始,到N的所有整数,然后数一下其中出现的所有"1"的个数。
例如:
N=2,写下1,2。这样只出现了1个"1"
N=12,写下 1,2,3,4,5,6,7,8,9,10,11,12。这样"1"的个数是5
请写出一个函数,返回1到N之间出现"1"的个数,比如 f(12)=5
一开始看这个就想到遍历1-N,每个数里面的1数量加起来,这样算法复杂度为 N乘N的位数,即O(NLogN)。后来看了原作所写,这个问题有复杂度为o(LogN)的解,于是思考了一番,用递归实现了。
同时写上O(NLogN)的遍历算法和原文作者的算法以作比较
2
3 interface Algrithm {
4 public int resolve(int number);
5 }
6
7 class RecursiveAlgrithm implements Algrithm {// 按位递归算法,o(LogN)
8 @Override
9 public int resolve(int number) {
10 if (number == 0) {
11 return 0;
12 }
13 int length = (int) Math.log10(number);
14 if (length == 0) {
15 return 1;
16 } else {
17 int currentLvl = scale(10, length);
18 int head = number / currentLvl;
19 if (head > 1) {
20 return currentLvl + (head) * resolve(currentLvl - 1)
21 + resolve(number - head * currentLvl);
22 } else {
23 return number - currentLvl + 1 + (head)
24 * resolve(currentLvl - 1)
25 + resolve(number - currentLvl);
26 }
27 }
28 }
29
30 public int scale(int a, int b) { //指数运算
31 int res = 1;
32 for (int i = 0; i < b; i++) {
33 res = a * res;
34 }
35 return res;
36 }
37 }
38
39 class EnumerateAlgrithm implements Algrithm {// 遍历算法,o(NLogN)
40
41 @Override
42 public int resolve(int number) {
43 int sum = 0;
44 for (int i = 0; i <= number; i++) {
45 sum += findInSingleNum(i);
46 }
47 return sum;
48 }
49
50 private int findInSingleNum(int number) {//单个数中包含'1'数量
51 int res = 0;
52 while (number > 0) {
53 if (number % 10 == 1) {
54 res++;
55 }
56 number = number / 10;
57 }
58 return res;
59 }
60 }
61
62 class ReferedAlgrithm implements Algrithm {//原作者的算法,o(LogN)
63 @Override
64 public int resolve(int n) {
65 int count = 0;
66 int factor = 1;
67 int lower;
68 int current;
69 int higher;
70 while (n / factor != 0) {
71 lower = n - (n / factor) * factor;
72 current = (n / factor) % 10;
73 higher = n / (factor * 10);
74 switch (current) {
75 case 0:
76 count += higher * factor;
77 break;
78 case 1:
79 count += higher * factor + lower + 1;
80 break;
81 default:
82 count += (higher + 1) * factor;
83 }
84 factor *= 10;
85 }
86 return count;
87
88 }
89 }
90
91 public class MsPuzzle {
92
93 public static void calculateWith(Algrithm al) {
94 long start = System.nanoTime();
95 int res = al.resolve(100000000);
96 long end = System.nanoTime();
97 System.out.println(al.getClass().getName()+":"+res);
98 System.out.println("time cost:" + (end - start) + "ns");
99 }
100
101 public static void main(String[] arg) {
102 MsPuzzle.calculateWith(new RecursiveAlgrithm());
103 MsPuzzle.calculateWith(new ReferedAlgrithm());
104 MsPuzzle.calculateWith(new EnumerateAlgrithm());
105 }
106 }
107
由于ReferedAlgrithm和RecursiveAlgrithm的算法太快,以至于以毫秒计时都是0ms,因此改用纳秒
output:
puzzles.RecursiveAlgrithm:80000001
time cost:96940ns
puzzles.ReferedAlgrithm:80000001
time cost:4191ns
puzzles.EnumerateAlgrithm:80000001
time cost:25053647670ns
可以看到原作者的算法是最快的,递归算法虽然算法复杂度同为o(LogN),不过递归在性能上不如循环,因此也差了一个数量级
而最后o(NLogN)复杂度的算法,性能则整整差了6个数量级以上...
这个差距,大概抵得上20年的硬件性能的发展了吧.
各位有兴趣也可以用C#分别实现下这几种算法,看看和java耗时相比如何^^