“年轻就该做点有趣的事情”
首先去实现一个斐波那契数列,以前在剑指offer上有写过,但是题目有几个版本,例如第一个数为0还是1,例如今天我遇到了一家很有趣的一个架构师就告诉我了在整个正负数都有解。
其实到昨天我才知道原来assert主要是在程序里写测试用例的。
这个数列是非常经典的递归问题。
他跟我讲最开始来个简单点的,我也就没考虑太多,甚至没有判断正负的问题,就算之前的我不知道整个整数的解也算是疏忽,以后的输入应该考虑更全面:
以下是我的版本:
private static int fibonacci(int n) {
// implement this...
//从第三位开始每个数字都是前两个数字的和
if(n == 0)
{
return 0;
}
else if(n == -1)
{
return 1;
}
else if(n == -2)
{
return -1;
}
else if(n < -2)
{
int a = fibonacci(n + 1);
int b = fibonacci(n + 2);
if(a < 0)
{
a = -a;
}
if(b < 0)
{
b = -b;
}
if(n % 2 == 0)
{
return - (a + b);
}
return a + b;
}
//当输入1,2
else if(n == 1 || n == 2)
{
return 1;
}
//大于1,2递归调用
else
{
return fibonacci(n - 1) + fibonacci(n - 2);
}
果然被批评了,但是连博主自己都感觉十分的丑陋,功能算是实现了,但是实在很难说的上是优雅。因此对方提出了让我深入理解f(n)=f(n-1)+f(n-2)的精髓,我开始思考,给出了更加精简的版本,代码已经丢失,我就不贴出来了,总之只是减少了代码量,但是远远没有达到优雅的地步。
后来他继续提醒我再去理解f(n)=f(n-1)+f(n-2),f(n)=f(n-1)+f(n-2),f(n)-f(n-1)=f(n-2)
我隐约感到了这个公式的内涵,但是又一下想不出他想要的方法,我仍然是把正负数分开来处理,其实这个公式只要解决0旁边几个数字,就可以用个通项公式去递归,这一点我已经理解了,但是很难一下子转变成代码,就像他说的惯性思维的barrier。最后我向他请教了。
他给我演示了两行代码,我还是很吃惊的。
if (n == 1 || n == 2) return 1;
return n > 2 ? fibonacci(n - 1) + fibonacci(n - 2) : fibonacci(n + 2) - fibonacci(n + 1);
好吧。我算是重新认识了斐波那契,fibonacci(n - 1) + fibonacci(n - 2)处理正数部分,fibonacci(n + 2) - fibonacci(n + 1)处理负数部分,省去了我感觉麻烦的符号判断。
好吧,事情还没完,接下来他让我用非递归的解决方案。他也下班了,我也赶着把公司的程序修改好,已经晚上九点我还没吃晚饭,但是仍然比较兴奋,所以就打算继续写会儿再回去。这个时候offer已经不重要了哈哈,只有那份Cong!的快感。我赶出来的非递归方案如下:
/*
* 非递归实现
*/
private static int fibonacci(int n)
{
/*
* 这是关于0对称的,只考虑正数部分,负数并且为偶数的返回对称正数的负数。
*/
//首先求出n的绝对值,只考虑正数部分
int d = n < 0 ? (-n) : n;
//定义返回值result
int result;
if(d == 0) result = 0;
if(d == 1 || d == 2) result = 1;
else
{
//定义前两个引用
int previous1 = 1;
int previous2 = 1;
int count = 2;
//滑动
while(count < d)
{
int temp = previous2;
previous2 = previous1 + previous2;
previous1 = temp;
count ++;
}
result = previous2;
}
result = n < 0 && n % 2 == 0 ? (-result) : result;
return result;
测试用例如下:
System.out.println(fibonacci(2));
System.out.println(fibonacci(3));
System.out.println(fibonacci(4));
System.out.println(fibonacci(5));
System.out.println(fibonacci(6));
System.out.println(fibonacci(-1));
System.out.println(fibonacci(-2));
System.out.println(fibonacci(-3));
System.out.println(fibonacci(-4));
System.out.println(fibonacci(-5));
System.out.println(fibonacci(-6));
assert 1 == fibonacci(2);
assert 2 == fibonacci(3);
assert 3 == fibonacci(4);
assert 5 == fibonacci(5);
assert 8 == fibonacci(6);
assert 13 == fibonacci(7);
assert 6765 == fibonacci(20);
assert 1 == fibonacci(-1);
assert -3 == fibonacci(-4);
System.out.println("Cong!");
本质上这是一个动态规划的问题,分三步走:
- 确定数组状态含义
- 定义状态转移方程式
- 确定初始值
- 优化(不一定)
/**
* 动态规划
* 避免传统递归的重复计算
*/
public int fib(int n) {
if (n < 2) return n;
int dp1 = 0, dp2 = 1;
for (int i = 1; i < n; i++) {
int q = dp2;
dp2 = (dp2 + dp1) % (int)(1e9 + 7);
dp1 = q;
}
return dp2;
}
/**
* 正常递归算法
*/
public int fib2(int n) {
return n < 2 ? n : (fib(n - 1) + fib(n - 2)) % (int)(1e9 + 7);
}