同事提出一个问题,关于牛生小牛的算法,之前的牛生小牛的算法都是假设牛不死的情况下的,几年生一头,若干年后还有多少头,这个算法是有些变化的。

农场第一年有一头牛,假设这头牛每2年生一次小牛,小牛迅速长大,2年后又能生小牛,但是牛活到5岁的时候回自然的死去,那么求第N年还有多少牛。

我想,这个问题的难点在于,牛要死去。自然而然的,这里要进行设置一个条件,当牛的年龄达到5岁,让牛死去(就是让这个元素消失)。同事基于这种思路,写出了第一版本的代码

母牛2年生小牛 5年后并死去的算法_i++

按照题意逻辑理解,进行顺序逻辑判断,编程代码如下

<?php
$fun=function($n){
$list=[1];
while ($n--) {
foreach ($list as $k => $v) {
if($v%2==0)
{
$list[]=1;//生一头小牛
}
if($v==5)
{
unset($list[$k]);//到五岁了 牛死了
continue;
}
$list[$k]++;//牛长大了一岁
}
}
return count($list);//返回牛的数目
};
echo $fun(10);
exit();

这个代码逻辑非常清晰,是基础中的典范,然而,我们发现当年数增加的时候,内存会溢出,比如在求100年以后的时候

母牛2年生小牛 5年后并死去的算法_递归_02

然后他提出了改进方案使用array_pop回收

截图如下

母牛2年生小牛 5年后并死去的算法_i++_03

代码如下:

<?php
$fun1 = function($n){
$list = [1];
while($n--){
foreach($list as $k => $v){
if($v%2 == 0){
$list[] = 1;
}
if($v == 5){
//unset($list[$k]);
$list[$k] = array_pop($list);
continue;
}
$list[$k]++;
}
}
return count($list);
};

echo $fun1(100);
exit();

然而这样还是会爆出内存泄漏 而且多了一个notice

母牛2年生小牛 5年后并死去的算法_递归_04

针对牛的死去,以及再生,我通过牛的数量寻找规律想到了一个方案,不知道能不能被理解。

截图如下:

母牛2年生小牛 5年后并死去的算法_i++_05

代码如下:

<?php
$sum=get_cow_num(10);
var_dump($sum);
exit();

function get_cow_num($n)
{
$sum=1;
for ($i=1; $i < $n; $i++)
{
$sum = $i%2!=0 ? $sum*2 : $sum-peibona($i/2-1);//奇数年要翻倍 偶数年减去斐波那契数
}
return $sum;
}
function peibona($n)
{
if($n<=0){return 0;}
if($n<=2){return 1;}
return peibona($n-1)+peibona($n-2);
}

在计算100年以后的时候 30秒时间不足

 母牛2年生小牛 5年后并死去的算法_递归_06

我想办法延长时间

结果……

 母牛2年生小牛 5年后并死去的算法_i++_07

十分钟过去了,程序仍然没有停止的意思,真的是够够的了,看来递归不太适合,我猜测最后会因为递归,报错提示PHP不能递归超过99层,因为之前出过这样的问题。

那么我改进一下,采用曾经解决过的斐波那算法来解决这个问题。

母牛2年生小牛 5年后并死去的算法_i++_08

这牛数量也太多了吧……

 代码如下:

<?php
set_time_limit(0);
$sum=get_cow_num(100);
var_dump($sum);
exit();

function get_cow_num($n)
{
$sum=1;
for ($i=1; $i < $n; $i++)
{
$sum = $i%2!=0 ? $sum*2 : $sum-peibona($i/2-1);//奇数年要翻倍 偶数年减去斐波那契数
}
return $sum;
}
function peibona($n)
{
if($n<=0){return 0;}
if($n<=2){return 1;}
$array=array_fill(0,$n,0);
$array[0]=0;
$array[1]=1;
$array[2]=1;
for ($i=3; $i <= $n; $i++)
{
$array[$i]=$array[$i-1]+$array[$i-2];
}
return $array[$n];
}

这这段代码有没有优化的地方呢?当然有我们发现每次算斐波那切数都是要循环重置进行计算,为何不用静态缓存呢?

母牛2年生小牛 5年后并死去的算法_递归_09

代码如下:

<?php
set_time_limit(0);
$sum=get_cow_num(100);
var_dump($sum);
exit();

function get_cow_num($n)
{
$sum=1;
for ($i=1; $i < $n; $i++)
{
$sum = $i%2!=0 ? $sum*2 : $sum-peibona($i/2-1,$n/2-1);//奇数年要翻倍 偶数年减去斐波那契数
}
return $sum;
}
function peibona($n,$total_num)
{
static $array;
if(!isset($array[$n]))
{
if($n<=0)
{
$array[$n]=0;
}elseif($n<=2)
{
$array[$n]=1;
}
else
{
$array=array_fill(0,$n,0);
$array[0]=0;
$array[1]=1;
$array[2]=1;
for ($i=3; $i <= $n; $i++)
{
$array[$i]=$array[$i-1]+$array[$i-2];
}
}
}
return $array[$n];
}

这代码,哈哈哈哈,风骚的让人无法理解啊!

后来根据同事总结规律得出公式,采用偶数翻倍,奇数减去斐波那切数的公式,得到python代码

母牛2年生小牛 5年后并死去的算法_i++_10

这代码极其风骚,采用递归完成,不过我估计不用递归应该更快才对。

关于斐波那切数的通项公式可以参考百度百科 ​​https://baike.baidu.com/item/%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97/99145?fr=aladdin​

母牛2年生小牛 5年后并死去的算法_php_11