算法学习笔记——动态规划:高楼扔鸡蛋
LeetCode 887. 鸡蛋掉落
建筑有n
层(取值1,2,...n
),存在一个楼层F
(0<=F<=n
,注意F
取值比n
多一个),从高于F
的楼层扔鸡蛋,鸡蛋会碎;否则鸡蛋不会碎
给你k
枚相同的鸡蛋,每次可以在任意楼层向下扔鸡蛋(如果鸡蛋没碎,可以重复使用)问:在最坏情况下,至少要扔几次鸡蛋,才能确定F
楼层的位置
如k = 2, n = 6时,返回3(在3层处扔鸡蛋,然后若鸡蛋碎了,只剩下一颗鸡蛋,就只能用线性扫描1~2楼)
理解题意:
最坏情况的含义:就是一定要等到搜索区间穷尽时才找到目标楼层,即每次向下摔鸡蛋,不管碎或没碎,都只能将目标区间缩小一定程度
i.e. 应该排除在1楼做一次实验就刚好碎了,结果得到
F=0
这种“幸运成分”
而是应当认为,在1楼做一次实验,最坏情况是鸡蛋没碎,那么就只能继续在更高楼层去做实验
——每次扔鸡蛋会将高楼分为上下两部分,应该做最坏的打算,即包含F
的目标区间位于[剩余楼层数更多的那一部分]
思路:
- 如果不限制鸡蛋个数,显然用二分思路可以得到最少的尝试次数;
但问题是鸡蛋个数有限,二分不可行(假如只有一颗鸡蛋,碎了就没有机会实验了,于是只能线性扫描,从低楼层向高楼层逐次尝试)
能不能先用二分搜索,等到只剩一颗鸡蛋时,再执行线性扫描呢?
这样也不是最优解,比如有100层楼和2颗鸡蛋,在50楼扔一次,剩下49次线性扫描,这样就不如“十分”:第一个鸡蛋每隔10楼扔一次,碎了后用第二颗鸡蛋线性扫描10层楼,总次数不超过20次。
因此,此题不能轻易看出最佳策略,只能穷举所有可能性并取最值(然后用动态规划进行优化)
- 如果在某一层楼扔了一个鸡蛋,结果会将整幢楼划分为下面部分的一号楼和上面部分的二号楼(收缩了
F
的目标区间)
如果鸡蛋碎了,应该到一号楼中寻找F
;
如果鸡蛋没碎,应该到二号楼中寻找F
(有种二分搜索的感觉)
同时,要注意题目求的是最坏情况:每次扔鸡蛋会将高楼分为上下两部分,应该做最坏的打算,即总认为包含F
的目标区间位于[剩余楼层数更多的那一部分]
根据上面的分析,我们只需关注目前的搜索区间的长度(区间中剩余的楼层数)
当搜索区间长度为0:找到了目标楼层
当搜索区间长度为1:相当于在n=1
的楼中寻找F
,此时仍需扔一次鸡蛋,确定F=0/1
从上面的思路出发,找出状态和选择,然后穷举所有可能,先得到暴力解法,然后优化即可
状态:目前剩下的搜索区间长度和剩余的鸡蛋数
选择:在哪一层楼扔鸡蛋dp数组的定义:
dp[k][n]
代表有n
层楼和k
个鸡蛋时,最坏情况下确定F
值的最少次数状态转移:在
n
层楼的i
层扔鸡蛋,本身扔鸡蛋次数+1
鸡蛋没碎,dp[k][n]=1+dp[k][n-i]
,搜索区间为下半部分
鸡蛋碎了,dp[k][n]=1+dp[k-1][i-1]
,搜索区间为上半部分
首先,考虑最坏情况,要求dp[k][n]
取两者中的最大值(在低层扔鸡蛋,鸡蛋碎了需要搜索上半部分,这是最坏情况;在高层扔鸡蛋,鸡蛋没碎需要搜索下半部分,这是最坏情况;)
其次,我们不知道在n
层楼的哪一层开始扔鸡蛋能得到最少的尝试次数,因此需要遍历尝试并比较所有可能的层数i
(1<=i<=n
)至于下次怎么做选择不用关心,交给递归完成,最终对各个选择的代价取最小值即为最优解base case:
k==1
,dp[1][n]=n
只有一颗鸡蛋,必须线性扫描(k==0
无意义)
n==0
,dp[k][0]=0
,搜索区间长度为0,代表已经找出F
实现:
动态规划不一定非要用dp数组,对于这题用递归更方便
写出递归的暴力解法后,用备忘录优化即可达到dp数组同样的复杂度
class Solution:def superEggDrop(self, k: int, n: int) -> int:memo = dict() # 备忘录def dp(k, n):# dp[k][n]代表有n层楼和k个鸡蛋时,最坏情况下确定F值的最少次数# base case# k==1,dp[1][n]=n,只有一颗鸡蛋,必须线性扫描(k==0无意义)# n==0,dp[k][0]=0,搜索区间长度为0,代表已经找出Fif k == 1:return nif n == 0:return 0if (k, n) not in memo:# 我们不知道在n层楼的哪一层开始扔鸡蛋是最好的选择,因此需要遍历尝试所有可能的层数i(`1<=i<=n`),取最小值minans = float('inf')for i in range(1, n + 1):ans = min(ans, 1 + max(dp(k - 1, i - 1), dp(k, n - i))) # 在碎了/没碎中,取最坏情况maxmemo[(k, n)] = ansreturn memo[(k, n)] return dp(k,n)
子问题数目=状态总数=KN,子问题本身复杂度=N(dp函数中的for循环)
总复杂度O(kN^2)
优化效率:二分搜索优化
注意,二分搜索优化与之前提到的用二分思路扔鸡蛋没有任何关系,能用二分搜索仅仅是因为这个问题的
dp
函数具有单调性,因而可以用二分搜索来快速寻找最值(具有单调性的问题,大多可以通过二分搜索来优化)
回顾dp(k,n)
定义:有n
层楼和k
个鸡蛋时,最坏情况下确定F
值的最少次数
注意到这样一个事实:对于dp(k,n)
,当k
固定时,整个函数的值随着n
的增大而单调递增
- 我们之前所做的,就是对于
i (1<=i<=n)
求函数max(dp(k-1, i-1), dp(k, n-i))+1
的值,然后找到[使函数取最小值的i
值] - 在子问题中,
k
和n
可以是为常数,则dp(k-1, i-1)
随着i
单调递增,而dp(k, n-i)
随着i
单调递减,画图表示为
- 最终,问题转化为:两个随着
i
单调变化的函数组合称为另一个函数(上图红色部分),求这个函数的山谷(Valley)值
实现:
对于i
,维护其左右边界l
和r
,每次使用二分法取值i=mid=(l+r)//2
,比较dp(k-1, i-1)
和dp(k, n-i)
,可以判断:若该处位于山谷值左侧,向右收缩区间;否则向左收缩区间(并不断更新红色部分的函数的最小值)
class Solution:def superEggDrop(self, k: int, n: int) -> int:memo = dict() # 备忘录def dp(k, n):# dp[k][n]代表有n层楼和k个鸡蛋时,最坏情况下确定F值的最少次数# base case# k==1,dp[1][n]=n,只有一颗鸡蛋,必须线性扫描(k==0无意义)# n==0,dp[k][0]=0,搜索区间长度为0,代表已经找出Fif k == 1:return nif n == 0:return 0if (k, n) not in memo:# 我们不知道在n层楼的哪一层开始扔鸡蛋是最好的选择,因此要尝试所有可能的层数i(1<=i<=n),取最小值minans = float('inf')# for i in range(1, n + 1):# ans = min(ans, 1 + max(dp(k - 1, i - 1), dp(k, n - i))) # 在碎了/没碎中,取最坏情况max# 二分法求山谷值,代替线性搜索l, r = 1, nwhile l <= r:mid = (l + r) // 2i = midbroken = dp(k - 1, i - 1) # 碎了notBroken = dp(k, n - i) # 没碎if broken < notBroken: # 该处位于山谷值左侧l = mid + 1ans = min(ans, notBroken + 1)else:r = mid - 1ans = min(ans, broken + 1)memo[(k, n)] = ansreturn memo[(k, n)]return dp(k, n)
算法学习笔记——动态规划:高楼扔鸡蛋相关推荐
- 算法学习笔记——动态规划:戳气球
LeetCode 312. 戳气球 数组 nums 中,保存了每个气球上的数字,戳破一个气球,得分是nums[i - 1] * nums[i] * nums[i + 1](若越界,认为两个边界上有数值 ...
- 高楼扔鸡蛋——动态规划问题
今天算法课大伙做了一份课堂测验,是有关一维以及二维动态规划问题的,其中最后一天压轴的就是这道高楼扔鸡蛋问题. 在我得意洋洋以为能早早做完这份测验然后交卷的时候,我在这道题上摁是扣了半小时头皮,也没写出 ...
- java动态规划鸡蛋问题_动态规划系列/高楼扔鸡蛋问题.md · lipengfei/fucking-algorithm - Gitee.com...
# 经典动态规划问题:高楼扔鸡蛋 今天要聊一个很经典的算法问题,若干层楼,若干个鸡蛋,让你算出最少的尝试次数,找到鸡蛋恰好摔不碎的那层楼.国内大厂以及谷歌脸书面试都经常考察这道题,只不过他们觉得扔鸡蛋 ...
- 高楼扔鸡蛋问题-经典动态规划
文章目录 1. 高楼扔鸡蛋 2. 猜数字大小 1. 高楼扔鸡蛋 给你 k 枚相同的鸡蛋,并可以使用一栋从第 1 层到第 n 层共有 n 层楼的建筑. 已知存在楼层 f ,满足 0 <= f &l ...
- 高楼扔鸡蛋问题 - 动态规划+反推演绎
对于高楼扔鸡蛋问题,本文尝试反其道而行之:首先描述一个普适的高楼扔鸡蛋问题,然后利用动态规划法解决扔鸡蛋次数的问题,最后由获取次数的答案反推出扔鸡蛋的方法. 这种由次数答案反推出方法的演绎方式令人有点 ...
- 两个字符串的最长公共子序列长度_算法学习笔记(58): 最长公共子序列
(为什么都更了这么多篇笔记了,这时候才讲这么基础的内容呢?因为我本来以为LCS这种简单的DP不用讲的,结果CF不久前考了LCS的变式,然后我发现由于自己对LCS一点都不熟,居然写不出来 ,于是决定还是 ...
- Python最优化算法学习笔记(Gurobi)
微信公众号:数学建模与人工智能 github地址:https://github.com/QInzhengk/Math-Model-and-Machine-Learning Python最优化算法学习笔 ...
- 数学建模算法学习笔记
数学建模算法学习笔记 作为建模Man学习数学建模时做的笔记 参考文献: <数学建模姜启源第四版> 网上搜罗来的各种资料,侵删 1.线性预测 levinson durbin算法,自相关什么的 ...
- 【LeetCode系列】高楼扔鸡蛋
高楼扔鸡蛋 题目描述:面前有一栋楼从1到N共N层的楼,然后给你K个鸡蛋(K至少为1).现在确定这栋楼存在楼层0<=F<=N,在这层楼将鸡蛋扔下去,鸡蛋恰好没摔碎(高于F的楼层都会碎,低于F ...
最新文章
- Redis AOF 持久化详解
- 自学python的网站-python有哪些学习网站
- Ex 2_5 求解递推式..._第三次作业
- apache mahout_Apache Mahout:入门
- STM32F1笔记(十一)ADC
- windows IOCP模型
- 为什么需要一个激励函数
- 留言板asp mysql,asp留言板(asp留言板源代码)
- Excel2007快捷键大全
- 如何成为优秀的网络安全工程师(转载)
- 一个好用的项目工时管理系统
- 逃跑吧少年服务器维护中怎么回事,逃跑吧少年7月6日更新维护公告
- OpenCV快速入门篇(Python实现)
- 113种渗透测试工具合集(全网最全)
- 关于清理电脑系统垃圾的batch文件
- matlab线性代数(diag)
- toybox 和 busybox 的作用
- Cpython环境配置
- xman-level5-mprotect利用
- java jolt_Java使用Jolt连接Tuxedo服务器
热门文章
- java脚本语言是什么_什么是脚本语言
- [YOLO] yolov3、yolov4、yolov5改进汇总
- Android开发基础之控件EditText
- 斐讯phicomm原厂固件桥接AP设置图文教程
- 盗链是什么?如何防止盗链?
- Archery通过OpenResty代理转发后登录一直提示Forbidden
- 淘宝/天猫搜索同款的商品 API 接口返回值说明
- 常用工作方法总结(7S、SWOT分析、PDCA循环、SMART原则、6W2H、时间管理、WBS、二八原则)
- bindService
- 图像学习之如何理解方向梯度直方图HOG(Histogram Of Gradient)