题目源头
这道题目是前些日子,面试阿里的时候,给的一道在线测评算法题
题目描述
大海中央的一座孤岛,上面本来没有老鼠。有一艘路过的船只在附近沉没了,船上 x 对 新出生的老鼠,顺着船只残骸漂流到小岛上。
1. 每两个月一次生产,每次出生 2对 老鼠
2. 老鼠的寿命是 5个月,也就是说一辈子可以生产 2次
3. 这个岛上,每个月因为神秘原因,假如存在 3个月大的老鼠,会死去一对
问 n个月后,岛上有多少只老鼠 (假设存活率 100% )
解题思路
1
- 如果只看第一个条件,颇有一点斐波那契数列的样子。但是这个题目,老鼠不是单纯的生,还有死、意外死,还有生的限制
2
- 进一步,可以看到,这个题目似乎老鼠是有几个状态的:新生的老鼠,意外死亡,正常死亡的老鼠等
- 最开始在想,是不是可以存在几个状态转移方程,譬如fn=nx,n个月时该状态下的老鼠数量,多个状态转移与岛上的老鼠有什么关系,去寻找这种关系
- 但是,想了一会儿,发现这种思路,偏复杂,比如意外死亡是需要考虑老鼠的年龄的,并不是单纯的将现存的老鼠直接-1就可以的,必须是年龄到3个月的时候才可-1。那就说明,这种状态方程计算数目的时候,必须要引入老鼠的年龄的概念。
- 这里的动态转移方程:已经想到这个题目应该是可以利用动态规划的思想去解决的
3
- 从老鼠年龄的角度出发,记录不同的月份的老鼠的有多少只,岛上的老鼠数目,就是当前n个月,不同月份的老鼠数目和
思路图解
- 下文提到的如(1,2)表示,1个月后,年龄为2两个月的老鼠数目
0个月时
其实就是新生老鼠来到岛上的时候,只存在x只新生的老鼠
1个月时
- 约束:
- (1,1)的老鼠,只能由0个月时的0个月的老鼠(0,0)成长而来,计算时候只需要考虑(0,0)格子(当前月份的老鼠,只能由上一个月的老鼠成长而来)
- 同理,(1,2)有(0,1)
- 没有新生,(1,0)为0
2个月时
- 约束:
- 岛上开始出现2个月的老鼠(1,1)经过1个月的成长具备生育能力,(2,0)为2*(1,1)
3个月时
- 约束:
- 岛上开始出现3个月的老鼠,出现意外死亡,(3,3)=(2,2)-1
- (3,0)为0,因为(2,1)为0,不存在具备生育能力的老鼠
- (3,1),(3,2)分别由(2,0),(2,1)成长而来
4个月时
- 约束:
- (3,3)经过一个月的成长,又具备生育能力了(每两个月),因此(4,0)=2*(3,1)+2*(3,3)
5个月时
- 约束:
- 正常死亡,(5,5)由(4,4)成长而来,但是会死亡,所以(5,5)为0
- 其实这个状态是可以没有的,因为(n,5)恒为0,不存在5个月的老鼠,但是这里为了体现死亡状态,特地把这一行写出来
状态转移方程
得出状态转移方程
-
0个月 f0(n) = 2f1(n-1)+2f3(n-1) //1个月,3个月的老鼠剩余
-
1个月 f1(n) = f0(n-1) //0个月的老鼠成长
-
2个月 f2(n) = f1(n-1) //1个月的老鼠成长
-
3个月 f3(n) = f2(n-1)-1 假设f2(n-1)>0 //2个月的老鼠成长,意外死亡1对
-
4个月 f4(n) = f3(n-1) //3个月的老鼠成长
-
5个月 f5(n) = 0 //4个月成长的老鼠,死亡
-
总的老鼠:total[n] = f0+f1+f2+f3+f4+f5
// f0(n) n个月时,0个月的老鼠有几只
// 从上面其实可以看出,再强调一次,第n个月老鼠数目,只与n-1个月时的不同月份的老鼠有关(后面代码优化的时候会用到的)
一个二维数组记录n+1行,6列,行数表示当前时间,列数表述当前n个月,不同年龄的老鼠array[n+1][6]
0个月时,[x,0,0,0,0,0]
1个月时,根据array[0]进行计算
2个月时,根据array[1]进行计算
3个月时,根据array[2]进行计算
.....
- **总的老鼠:total[n] = array[n][0]+array[n][1]+array[n][2]+array[n][3]+array[n][4]+array[n][5]
空间复杂度优化
- 其实,思路解析到这里,如果有接触过01背包问题的同学,已经感觉到非常熟悉了,01背包问题的优化,最后会有存在对空间复杂度的优化
- 这里用二维数组进行记录,array[n]的状态,只跟array[n-1]有关,其实不需要O(n)的空间复杂度的
- 只需要array[2][6]的数组即可!
0个月时,[x,0,0,0,0,0]
1个月时,根据array[0]进行计算(跟前面一样)
2个月时,根据array[1]进行计算,记录在array[0]
(因为,原先的array[0]已经成长为array[1]了,array[0]已经是废弃数据了,因此原先的array[2]数据可以记录在array[0],上一个月数据就是array[1])
3个月时,根据array[0]进行计算,记录在array[1]
.....
实际写代码的时候,只要当前月份除2取余数,得出记录月份以及上一月的计算基础
代码
- 阿里的测评,对面试者,并没有类似leetcode提交代码,看是否ac。而是给出思路,手写代码。所以这边只提供思路,不提供解题代码~(其实,上面的分析,理解完,代码已经非常清晰了)
- 我自己用js将上面的状态转移方程呈现出来,以及考虑优化,代码在50行以内
- 大家可以用自己熟悉的语言写写看
last
不清楚上面是否有遗漏的case,欢迎交流,提供不一样的解题思路~