LeetCode 887. 鸡蛋掉落
建筑有n层(取值1,2,...n),存在一个楼层F0<=F<=n,注意F取值比n多一个),从高于F的楼层扔鸡蛋,鸡蛋会碎;否则鸡蛋不会碎
给你k枚相同的鸡蛋,每次可以在任意楼层向下扔鸡蛋(如果鸡蛋没碎,可以重复使用)问:在最坏情况下至少要扔几次鸡蛋,才能确定F楼层的位置
如k = 2, n = 6时,返回3(在3层处扔鸡蛋,然后若鸡蛋碎了,只剩下一颗鸡蛋,就只能用线性扫描1~2楼)

理解题意:
最坏情况的含义:就是一定要等到搜索区间穷尽时才找到目标楼层,即每次向下摔鸡蛋,不管碎或没碎,都只能将目标区间缩小一定程度

i.e. 应该排除在1楼做一次实验就刚好碎了,结果得到F=0这种“幸运成分”
而是应当认为,在1楼做一次实验,最坏情况是鸡蛋没碎,那么就只能继续在更高楼层去做实验
——每次扔鸡蛋会将高楼分为上下两部分,应该做最坏的打算,即包含F的目标区间位于[剩余楼层数更多的那一部分]

思路:

  1. 如果不限制鸡蛋个数,显然用二分思路可以得到最少的尝试次数;
    但问题是鸡蛋个数有限,二分不可行(假如只有一颗鸡蛋,碎了就没有机会实验了,于是只能线性扫描,从低楼层向高楼层逐次尝试)

能不能先用二分搜索,等到只剩一颗鸡蛋时,再执行线性扫描呢?
这样也不是最优解,比如有100层楼和2颗鸡蛋,在50楼扔一次,剩下49次线性扫描,这样就不如“十分”:第一个鸡蛋每隔10楼扔一次,碎了后用第二颗鸡蛋线性扫描10层楼,总次数不超过20次。
因此,此题不能轻易看出最佳策略,只能穷举所有可能性并取最值(然后用动态规划进行优化)

  1. 如果在某一层楼扔了一个鸡蛋,结果会将整幢楼划分为下面部分的一号楼和上面部分的二号楼(收缩了F的目标区间)
    如果鸡蛋碎了,应该到一号楼中寻找F
    如果鸡蛋没碎,应该到二号楼中寻找F

    (有种二分搜索的感觉)
    同时,要注意题目求的是最坏情况:每次扔鸡蛋会将高楼分为上下两部分,应该做最坏的打算,即总认为包含F的目标区间位于[剩余楼层数更多的那一部分]

根据上面的分析,我们只需关注目前的搜索区间的长度(区间中剩余的楼层数)
当搜索区间长度为0:找到了目标楼层
当搜索区间长度为1:相当于在n=1的楼中寻找F,此时仍需扔一次鸡蛋,确定F=0/1

  1. 从上面的思路出发,找出状态和选择,然后穷举所有可能,先得到暴力解法,然后优化即可
    状态:目前剩下的搜索区间长度和剩余的鸡蛋数
    选择:在哪一层楼扔鸡蛋

  2. dp数组的定义:dp[k][n]代表有n层楼和k个鸡蛋时,最坏情况下确定F值的最少次数

  3. 状态转移:在n层楼的i层扔鸡蛋,本身扔鸡蛋次数+1
    鸡蛋没碎,dp[k][n]=1+dp[k][n-i],搜索区间为下半部分
    鸡蛋碎了,dp[k][n]=1+dp[k-1][i-1],搜索区间为上半部分
    首先,考虑最坏情况,要求dp[k][n]取两者中的最大值(在低层扔鸡蛋,鸡蛋碎了需要搜索上半部分,这是最坏情况;在高层扔鸡蛋,鸡蛋没碎需要搜索下半部分,这是最坏情况;)
    其次,我们不知道在n层楼的哪一层开始扔鸡蛋能得到最少的尝试次数,因此需要遍历尝试并比较所有可能的层数i1<=i<=n)至于下次怎么做选择不用关心,交给递归完成,最终对各个选择的代价取最小值即为最优解

  4. base case:
    k==1dp[1][n]=n只有一颗鸡蛋,必须线性扫描(k==0无意义)
    n==0dp[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值]
  • 在子问题中,kn可以是为常数,则dp(k-1, i-1)随着i单调递增,而dp(k, n-i)随着i单调递减,画图表示为
  • 最终,问题转化为:两个随着i单调变化的函数组合称为另一个函数(上图红色部分),求这个函数的山谷(Valley)值

实现:
对于i,维护其左右边界lr,每次使用二分法取值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)

算法学习笔记——动态规划:高楼扔鸡蛋相关推荐

  1. 算法学习笔记——动态规划:戳气球

    LeetCode 312. 戳气球 数组 nums 中,保存了每个气球上的数字,戳破一个气球,得分是nums[i - 1] * nums[i] * nums[i + 1](若越界,认为两个边界上有数值 ...

  2. 高楼扔鸡蛋——动态规划问题

    今天算法课大伙做了一份课堂测验,是有关一维以及二维动态规划问题的,其中最后一天压轴的就是这道高楼扔鸡蛋问题. 在我得意洋洋以为能早早做完这份测验然后交卷的时候,我在这道题上摁是扣了半小时头皮,也没写出 ...

  3. java动态规划鸡蛋问题_动态规划系列/高楼扔鸡蛋问题.md · lipengfei/fucking-algorithm - Gitee.com...

    # 经典动态规划问题:高楼扔鸡蛋 今天要聊一个很经典的算法问题,若干层楼,若干个鸡蛋,让你算出最少的尝试次数,找到鸡蛋恰好摔不碎的那层楼.国内大厂以及谷歌脸书面试都经常考察这道题,只不过他们觉得扔鸡蛋 ...

  4. 高楼扔鸡蛋问题-经典动态规划

    文章目录 1. 高楼扔鸡蛋 2. 猜数字大小 1. 高楼扔鸡蛋 给你 k 枚相同的鸡蛋,并可以使用一栋从第 1 层到第 n 层共有 n 层楼的建筑. 已知存在楼层 f ,满足 0 <= f &l ...

  5. 高楼扔鸡蛋问题 - 动态规划+反推演绎

    对于高楼扔鸡蛋问题,本文尝试反其道而行之:首先描述一个普适的高楼扔鸡蛋问题,然后利用动态规划法解决扔鸡蛋次数的问题,最后由获取次数的答案反推出扔鸡蛋的方法. 这种由次数答案反推出方法的演绎方式令人有点 ...

  6. 两个字符串的最长公共子序列长度_算法学习笔记(58): 最长公共子序列

    (为什么都更了这么多篇笔记了,这时候才讲这么基础的内容呢?因为我本来以为LCS这种简单的DP不用讲的,结果CF不久前考了LCS的变式,然后我发现由于自己对LCS一点都不熟,居然写不出来 ,于是决定还是 ...

  7. Python最优化算法学习笔记(Gurobi)

    微信公众号:数学建模与人工智能 github地址:https://github.com/QInzhengk/Math-Model-and-Machine-Learning Python最优化算法学习笔 ...

  8. 数学建模算法学习笔记

    数学建模算法学习笔记 作为建模Man学习数学建模时做的笔记 参考文献: <数学建模姜启源第四版> 网上搜罗来的各种资料,侵删 1.线性预测 levinson durbin算法,自相关什么的 ...

  9. 【LeetCode系列】高楼扔鸡蛋

    高楼扔鸡蛋 题目描述:面前有一栋楼从1到N共N层的楼,然后给你K个鸡蛋(K至少为1).现在确定这栋楼存在楼层0<=F<=N,在这层楼将鸡蛋扔下去,鸡蛋恰好没摔碎(高于F的楼层都会碎,低于F ...

最新文章

  1. Redis AOF 持久化详解
  2. 自学python的网站-python有哪些学习网站
  3. Ex 2_5 求解递推式..._第三次作业
  4. apache mahout_Apache Mahout:入门
  5. STM32F1笔记(十一)ADC
  6. windows IOCP模型
  7. 为什么需要一个激励函数
  8. 留言板asp mysql,asp留言板(asp留言板源代码)
  9. Excel2007快捷键大全
  10. 如何成为优秀的网络安全工程师(转载)
  11. 一个好用的项目工时管理系统
  12. 逃跑吧少年服务器维护中怎么回事,逃跑吧少年7月6日更新维护公告
  13. OpenCV快速入门篇(Python实现)
  14. 113种渗透测试工具合集(全网最全)
  15. 关于清理电脑系统垃圾的batch文件
  16. matlab线性代数(diag)
  17. toybox 和 busybox 的作用
  18. Cpython环境配置
  19. xman-level5-mprotect利用
  20. java jolt_Java使用Jolt连接Tuxedo服务器

热门文章

  1. java脚本语言是什么_什么是脚本语言
  2. [YOLO] yolov3、yolov4、yolov5改进汇总
  3. Android开发基础之控件EditText
  4. 斐讯phicomm原厂固件桥接AP设置图文教程
  5. 盗链是什么?如何防止盗链?
  6. Archery通过OpenResty代理转发后登录一直提示Forbidden
  7. 淘宝/天猫搜索同款的商品 API 接口返回值说明
  8. 常用工作方法总结(7S、SWOT分析、PDCA循环、SMART原则、6W2H、时间管理、WBS、二八原则)
  9. bindService
  10. 图像学习之如何理解方向梯度直方图HOG(Histogram Of Gradient)