首页 > 代码库 > 项目中的有趣题目 -- 吃饺子问题
项目中的有趣题目 -- 吃饺子问题
题目描述:
近日,项目中偶遇一个有趣的题目,感慨多多,备忘之。抽象出来,大致是:
桌上一共有100个饺子,其中有10个饺子包了硬币,问:连续吃到硬币的期望次数是多少次?
首先,定义一下这里的连续,如果我们将吃饺子的顺序抽象为一个100位的二进制数。并且吃到饺子表示为1,没吃到则为0,那么:
- 如果一次和第二次吃到,那么可表示为: 110.....,那么这里的连续吃到的次数为1.
- 如果数字为: ...1111.... ,那么这里连续的4个1表示3次连续,也就是说只要连续,就算1次。
期望次数,也就是说我们需要算出0个连续,1个连续,....9个连续。这10个计数。
注:这里的连续其实就是取决于当前位置的前一个位置的状态即可。因此可以用DP来解决。
此题目是一个数数的问题,显然可以使用DP来进行求解。
global[i][j][k] : 表示前i个饺子中,已经吃到了j个硬币,并且连续的次数为k。
local[i][j][k] : 表示前i个饺子中,吃到了j个硬币,并且第k次连续发生在第i个位置上。(也就是说第i-1次吃到的也是饺子)。并且连续的次数为k。
local[i][j][k] = local[i-1][j-1][k-1] + global[i-3][j-2][k-1]
global[i][j][k] = local[i][j][k] + global[i-2][j][k] + global[i-2][j-1][k] + local[i-3][j-2][k-1] + global[i-3][j-1][k]
显然上式以最后一次是否连续作为子问题是不明智的,递推关系式太过复杂。
下面从另一个角度来分析:
以最后两位的状态来决定:
- 11 : 那就是连续一次,
- 10 , 01, 00 , 都没有连续,子问题直接递推即可。
因此,定义如下子问题:
f[i][j][k][0/1] : 表示前i个饺子中,吃到了j个硬币,并且有k个连续。且最后一个是0/1的次数。
那么我们要求的就是: f[100][10][k][0] + f[100][10][k][1]; k = {1,2,3,...,9}
注意:这里的最后一维其实就是一个0/1的记录,当然你也可以使用两个三维数组来表示。Python代码实现如下:
结果为: 0.9
由于这个函数在目前的项目中可能会出现多次调用的情况。那么计算次数要尽量低。另一方面,这个内存占用非常大,在项目中n和m的取值大概是10^5。
如何快速有效的实现。
目前的方案是:由于这是一个三位的数组,那么每一维其实是一个平面,那么我隔固定的区间保存这样的一个平面,比如说,保存f[i][j][100], f[i][j][200], ...,这样下去,在计算时,选取最接近的平面,向下推。(因为每一个平面的计算只依赖于上一个平面)。
上述讲法可能有点儿晦涩,类比一个简单的例子,就清晰了:
比如,我们现在要编写一个求解斐波拉契数的函数,这个函数调用次数非常的频繁,那么我们回想到使用备忘录,但是呢,我们现在的内存十分有限,不可能将前面计算过的值全部保存下来。那么我们保存哪些值呢,很简单,因为每一个数之依赖于它的前两个数,因此,我们隔一段区间保存两个数。比如,我们保存 f[1000], f[1001], f[2000], f[2001],f[3000],f[3001],....我们计算f[3076]的时候,直接从f[3000],f[3001]开始算起即可,不需要再从0开始,这样既降低了空间的消耗的同时尽可能地减少时间的消耗。
其实这就是一个一维的情况,每次保存的是两个数。上面的三维矩阵,每次保存的是一个二维的数组。
Final words:
- 这里调BUG时,对Python这种动态语言又进了一步,下面的代码正确吗?自己试试吧:
这样定义f和g两个数组对吗? - DP的子问题定义是technique。子问题定义的好坏直接影响递推式的复杂程度。
项目中的有趣题目 -- 吃饺子问题
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。