动态规划主题专杀-高楼扔鸡蛋-思路自我解剖

做这种题再次强调一点,思路远比代码重要,思路对了,即使代码错了,错的也是细枝末节,思路错了,那就是个0.

所以本文并不想着重讲本题的算法代码,代码解法网上有得是。这里着重剖析自己的思路问题。

 

题干:

 

这题乍一看有点奇怪,但实际上可以这样理解: 扔鸡蛋有碎和不碎两种结果,碎了就不能再用了,不碎就可以反复用,而鸡蛋的个数就代表着允许的碎的次数的上限,至于从哪个楼层去扔,什么顺序扔,什么原则扔,则是具体方案的问题了。

 

这题是没有简单直接的方案的,因为基本上在这个场景下,任何能够通过直觉的方案,都可以证明未必是最优的,比如从最底层楼开始扔鸡蛋,一层层往上,直到碎了为止,显然这样是最省鸡蛋的,但是最费次数,基本上类似于数据库的table scan,显然达不到“至少”这个要求,而所谓“最坏”,这里的理解就有点绕逻辑了,我甚至认为题干这种描述是有点不负责任的,竟然不说清楚什么叫“最坏”,但总之决不能简单理解为等于楼层的高度,很显然这里有种条件中的条件的意味。

如果让我把这个“最坏”的定义说清楚,可以这么理解:请你给出一套方案(如何形式化地定义“方案”后面会说),按照这个方案,我可以算出一张唯一确定的表来如下:

F的实际值最少扔鸡蛋次数
0a
1

b

2c
......
Nx


第一列F对应实际值(但是主观上不事先知道,有点像薛定谔的猫),第二列代表对应行F值时该套方案要最少扔几次鸡蛋,然后取这个表第二列中的最大值作为该方案的“最坏”情况。这样就相当于定义了一个从方案到最坏扔鸡蛋次数的映射:

badLuckCount = F(solution)

优化目标就是求一个solution,使得badLuckCount最小。

这样把表一列,着实对理解题干很有帮助,可是对解题方法反而更迷惑了,这种在高个子中取矮子的操作该怎么搞?

如果逻辑严密的话,到这一步其实挺让人头疼的。

 

那么如何找出统一的递推公式呢?这就要再深入理解一下上面提到的“最坏”了,上面已经给了一张表,可是仍然没有形式化地描述什么是“solution”,上面说了,一个方案可以对应一张表,那假如我告诉你我手头有一个方案,我该如何形式化地描述它呢?是不是直接给出一个具体的扔鸡蛋楼层的序列呢(比如4,7,5...)?当然不是,因为这完全没有体现出求最优的思路,要知道每扔一次鸡蛋,就多获得一部分信息,随后的决策就“更有迹可循”,所以形式化的方案应该是这样:“先在X层扔,若鸡蛋碎了,则再在Y层扔,若鸡蛋没碎,则再在Z层扔,这样拆解下去”,这样大家可以想象,最终形成了一颗决策树,也就是说,形式化的“方案”应该是一颗决策树。这个决策树套用到上面的表中的每一行中的第一列,可以算出该行的第二列,并得到唯一的一个扔鸡蛋路径。

 

由此,问题可以转化为:求一颗最优决策树,符合上面的最优化表格要求,决策树的高度(即树根到叶子节点的最长路径)就代表该方案的最多扔鸡蛋次数

 

而决策树的形式首先得有个树根,然后就是比较简单了,根据鸡蛋碎还是不碎进行而分叉,分到新的未扔过的楼层,直到某一层的节点能够判定结果为止,也就是这个决策树的叶子节点,那么该树从树根到所有叶子节点这个路径长度,取个max,就是该方案的最坏情况扔鸡蛋次数了。

 

下面其实就是通过一系列的动态规划方案来确定这个树的样子。

如果让人来做,自然会想到先确立树根,人会怎么做呢?一个个试呗!从0到N分别做树根,把每种方案的值都算出来,再取个最小值,不就完了吗?

先来考虑一个状态公式,要让该公式比较容易做递推。根据我们上面决策树的思想,每一次递推都是在扔完鸡蛋判定碎还是不碎的情况下做的,而碎和不碎分别会导致进入什么新的状态呢?如果在第i层扔鸡蛋,碎了,则会导致我们的搜索空间从原空间,进入以i为分界线的下层空间,反之则进入上层空间,但无论如何,都是一个空间段,所以状态最好用空间段表示,我们用low, high表示这个空间段的下限和上限的话,状态公式可以表示为:dp(low, high, K), 当然其实我这里列出这个公式,是为了完整求出这个决策树,但仔细想一想,题干只要求次数而已,并不care决策树的具体形式,其实只需要使用dp(levels, K)就可以了,其中levels代表搜索空间总共有几层,who 踏马care 这些层是在什么位置,1到4跟6到9难道在鸡蛋数相同的情况下,次数有什么区别吗?当然没区别,由此就更简化了这个问题。

那么就按照状态公式为:dp(levels, K)来算

则最顶层的逻辑应该是:

int solutionCount = N;

for(int f=0;f<=N;f++){

solutionCount = min(solutionCount, max(左子树的高度,右子树的高度))

}

 

上面左子树和右子树分别代表鸡蛋碎还是不碎的两种情况,如果左子树代表蛋碎,右子树代表蛋不碎的情况,

那么

左子树高度=dp(f-1,K-1)

右子树的高度=dp(N-f, K)

 

至此,最让人头大的部分,完整的递推公式就出来了,base case实在太简单,只有一层楼,或者一个鸡蛋的情况大家自己去想吧,我就不说了,剩下别忘了用Memtable做一下去重,也不赘述了。

 

反思:这里可能大家也注意到我推理过程中,差点用了dp(low, high, K)这个状态公式,而实际上是不需要这么复杂的,我发现自己多次在动态规划类的题目中碰到这种情况,即有些题干只需要求次数,不需要求具体方案,这种题目往往意味着有比完整方案更简化的做法的,不要把简单问题复杂化了,否则纯属浪费自己的时间,如果面试中碰到而复杂化了,还容易翻车。

 

 

吐槽一下,我认为网上大部分答案都只是直接给递推公式而不说清楚统一递推公式的底层逻辑,或者一笔带过了,所以我这里特别强调说清楚底层逻辑。

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值