***************************************转载请注明出处:http://blog.csdn.net/lttree******************************************
一、 引言
今天早上,例行随便看看。
看到文章 -> 面试中如何剔除“鱼目混珠”的程序员
看到里面这段:
招聘程序设计人员,尤其是提到代码,最流行的将鱼目混珠的程序员剔除的问题是 “Fizz-Buzz” 测试。如果一个程序员无法在10-15分钟之间写出一个 Fizz-buzz,那他可能需要更多的锻炼,或许根本没有准备好。另外一个方法就是让他们写 Fibonacci series(斐波纳契数列),并请他们优化一下。大家都知道 Fibonacci 是非常常见的,但是你可能会很惊讶的看到这些程序员很难在之上写出这些数列,即使是在 IDE 上也写不出来。
恩,话说不懂什么事Fizz-buzz测试。。。
于是Wiki了一下 -> http://en.wikipedia.org/wiki/Fizz_buzz
好吧,就是个报数游戏,3或3的倍数 喊Fizz,5或5的倍数喊Buzz,如果既是3又是5的倍数喊FizzBuzz。
重点是后面的那个Fibonacci 的优化,
我只知道 递归和递推两种,上网搜了搜,果真优化很多
看到了 时间复杂度O(log(n)) 空间复杂度O(1)的方法。
就想学习一下
二、Fizz-Buzz
这个我觉得没有难度,
这是我写的:
<span style="font-family:Comic Sans MS;font-size:14px;">// Fizz Buzz void FizzBuzz( int n ) { bool isFZ; for( int i = 1 ; i <= n ; ++i ) { isFZ=false; if( i % 3 == 0 ) {cout<<"Fizz";isFZ=true;} if( i % 5 == 0 ) {cout<<"Buzz";isFZ=true;} if( !isFZ ) cout<<i; cout<<" "; if( i % 10 == 0 ) cout<<endl; } }</span>
很简单,但我总觉得有点繁杂,
希望会更好的方法的,留下代码,学习一下~
三、Fibonacci的优化
简单说一下Fibonacci 数列
有一种理想型生物,都拿兔子来说= =。
刚开始有这么一对兔子,每月初可以生一对兔子,而刚出生的兔子到第三个月初开始,也可以生每月初生一对兔子。
这样下去,到第n个月,会有多少对兔子?
来一个表格:
月份: 0
1 2
3 4 5
6 ...
n
①兔: 0
0 1
1 2 3
5 ...
n-1_成年兔+n-1_②兔
推导: n-1_成年兔 = n-2_成年兔+n-2_②兔,n-1_②兔=n-2_①兔
n-1_成年兔+n-1_②兔= n-2_兔总
②兔: 0
0 0
1 1 2
3 ...
n-1_①兔
成年兔: 0
1 1
1 2 3
5 ...
n-1_成年兔+n-1_②兔
兔总: 0
1 2
3 5 8
13 ...
n-1_兔总+n-2兔总
PS:①兔表示一个月大的兔子,②兔即两个月大兔子。里面的数字是兔子的对数。
这是推倒出来
第n个月的 ①兔对数 等于 该月成年兔对数
第n个月的 ②兔对数 等于 第 n-1月的①兔对数
第n个月的 成年兔对数 等于 第n-1月的成年兔对数+②兔对数
然后再根据n-1 推 n-2 的发现
第n个月兔子对数 等于 第n-1月 与 第n-2月 兔子对数之和。
这是按我的理解思路,讲述的。。有点绕,不知道懂了木有。。。
基本的Fibonacci概念说完了,现在看它们的解法:
1.最简单暴力好读的——递归方法
时间复杂度:O(n^2)
空间复杂度:数字过大可能导致 栈溢出
<span style="font-family:Comic Sans MS;font-size:14px;">int digui( int n ) { if( n == 0 ) return 0; else if( n == 1 ) return 1; else return ( digui(n-1) + digui(n-2) ); }</span>
2.省空间也省了时间的,递归进阶——递推方法
时间复杂度:O(n)
空间复杂度:O(n)
<span style="font-family:Comic Sans MS;font-size:14px;">// 普通递推算法 int* ditui( int n ) { int* arr = new int[n+1]; arr[0]=0,arr[1]=1; for( int i = 2 ; i <= n ; ++i ) arr[i]=arr[i-1]+arr[i-2]; return arr; }</span>
3.继续优化——优化递推法
我们可以看到,如果求第n个Fibonacci数,
我们只需要知道第n-1和第n-2个的Fibonacci数即可,
前面的不需要存储。
所以,就有了更优化,
时间复杂度:O(n)
空间复杂度:O(1)
<span style="font-family:Comic Sans MS;font-size:14px;">// 递推算法优化 int ditui_opt( int n ) { if( n < 2 ) return n; int i = 1,pre1=0,pre2=1; while( i < n ) { pre2 = pre2 + pre1; pre1 = pre2 - pre1; ++i; } return pre2; }</span>
4.更优化的——矩阵法
时间复杂度:O(log(n) )
空间复杂度:数字过大可能导致 栈溢出
递归、递推已经无法优化时间复杂度了,
空间都到了O(1)了,也没法再精进了,
so,有没有别的方法来进行进一步优化呢?
Of course!
我们可以发现,其实f(n) 都是 f(0)和f(1) 有关的:
f(2) = f(1) + f(0);
f(3) = f(2) + f(1) = 2*f(1) + f(0);
f(4) = f(3) + f(2) = 3*f(1) + 2*f(0);
.......
所以未来的f(n)一定等于:
f(n) = a*f(1) + b*f(0);
可是,如何求a和b呢?
通过矩阵行列式,可以推演出:
这样,关键就是求矩阵的次方了,
如果直接计算,那么时间复杂度是O(n),根本就没有什么优化。
所以,此时,我们就要用二分法(分治法)来解决
M^a = M^(a/2) * M^(a/2) = ....
这样,时间复杂度可以优化到O(log(n) )!
<span style="font-family:Comic Sans MS;font-size:14px;">// 矩阵优化 方法 // 构造个矩阵结构体 struct Matrix { int m0,m1,m2,m3; }; // 矩阵乘法 Matrix mat_mul( Matrix mtx1 , Matrix mtx2 ) { Matrix mat; mat.m0 = mtx1.m0 * mtx2.m0 + mtx1.m1 * mtx2.m2; mat.m1 = mtx1.m0 * mtx2.m1 + mtx1.m1 * mtx2.m3; mat.m2 = mtx1.m2 * mtx2.m0 + mtx1.m3 * mtx2.m2; mat.m3 = mtx1.m2 * mtx2.m1 + mtx1.m3 * mtx2.m3; return mat; } // 矩阵乘方 Matrix mat_pow( int k ) { Matrix mat; if( k == 1 ) { mat.m0=1; mat.m1=1; mat.m2=1; mat.m3=0; } else if( k % 2 == 0) { mat = mat_pow( k/2 ); mat = mat_mul( mat , mat ); } else { mat = mat_pow( (k - 1) / 2 ); mat = mat_mul( mat , mat ); mat = mat_mul( mat , mat_pow(1) ); } return mat; } // 最后求Fibonacci int fib_matrix( int n ) { if( n < 2 ) return n; Matrix mat; mat = mat_pow(n-1); int ans; ans = mat.m0 + mat.m1; return ans; }</span>
这种方法时间上压缩到了log(n),可是空间上,因为二分法,算是递归的过程,
有可能会导致 栈溢出。
于是乎,又有了优化方法。
5.优化中的优化
时间复杂度:O(log(n))
空间复杂度:O(1)
没错,就是O(1)。
压缩空间复杂度,而且是对于递归,
那就是用 递归 转 递推的方法,推导出的,
我对于这个方法,还是有些迷惑,没办法讲述太明白,
在转递推过程中,
会发现,并不是所有的数都需要存储,
于是就会用到 《编程之美》中的:
来考虑。
算法之路,博大精深啊。。
***************************************转载请注明出处:http://blog.csdn.net/lttree******************************************