题目描述

有 A 和 B 两种类型 的汤。一开始每种类型的汤有 n 毫升。有四种分配操作:
提供 100ml 的 汤A 和 0ml 的 汤B 。
提供 75ml 的 汤A 和 25ml 的 汤B 。
提供 50ml 的 汤A 和 50ml 的 汤B 。
提供 25ml 的 汤A 和 75ml 的 汤B 。
当我们把汤分配给某人之后,汤就没有了。每个回合,我们将从四种概率同为 0.25 的操作中进行分配选择。如果汤的剩余量不足以完成某次操作,我们将尽可能分配。当两种类型的汤都分配完时,停止操作。
注意 不存在先分配 100 ml 汤B 的操作。
需要返回的值: 汤A 先分配完的概率 + 汤A和汤B 同时分配完的概率 / 2。返回值在正确答案 10-5 的范围内将被认为是正确的。

示例 1:

输入: n = 50
输出: 0.62500
解释:如果我们选择前两个操作,A 首先将变为空。
对于第三个操作,A 和 B 会同时变为空。
对于第四个操作,B 首先将变为空。
所以 A 变为空的总概率加上 A 和 B 同时变为空的概率的一半是 0.25 *(1 + 1 + 0.5 + 0)= 0.625。

示例 2:

输入: n = 100
输出: 0.71875

提示:

0 <= n <= 109​​

方法一:动态规划(自底向上)

class Solution {public:double soupServings(int n) {// 如果n的值大等于4475,说明汤A肯定会先被分配完if(n >= 4475)   return 1.0;// /25方便计算,ceil向上取整n = ceil((double)n / 25);// 定义动态规划数组dp// dp[i][j]表示汤A和汤B分别剩余i和j时的概率值vector<vector<double>> dp(n+1, vector<double>(n+1));// dp[0][0]表示同时完成分配dp[0][0] = 0.5;// dp[0][j]表示汤A已经分配完,概率恒为1for(int i=1; i<=n ; i++){dp[0][i] = 1.0;}// 考虑剩下的情况,注意下标需要大于0,因此使用max函数来判断for(int i=1; i<=n ;i++){for(int j=1; j<=n; j++){dp[i][j] = 0.25 * (dp[max(0, i - 4)][j] + dp[max(0, i - 3)][max(0, j - 1)] + dp[max(0, i - 2)][max(0, j - 2)] + dp[max(0, i - 1)][max(0, j - 3)]); }}return dp[n][n];}
};

方法二:动态规划(自顶向下)

class Solution {public:double soupServings(int n) {// 如果n的值大等于4475,说明汤A肯定会先被分配完if(n >= 4475)   return 1.0;// /25方便计算,ceil向上取整n = ceil((double)n / 25);// 定义动态规划数组dp// dp[i][j]表示汤A和汤B分别剩余i和j时的概率值vector<vector<double>> dp(n+1, vector<double>(n+1));// dp[n][n]表示汤A和汤B分别剩余n的概率为1dp[n][n] = 1.0;// 定义temp为当前dp[i][j]对于分布的四种情况的概率值double temp = 0.0;// 对所有蓝色块进行计算for(int i=n; i>0; i--){for(int j=n; j>0; j--){// 汤B先被分配完,对最终结果没有影响if(dp[i][j] == 0)   continue;temp = 0.25 * dp[i][j];dp[max(0, i - 4)][j] += temp; dp[max(0, i - 3)][max(0, j - 1)] += temp;dp[max(0, i - 2)][max(0, j - 2)] += temp;dp[max(0, i - 1)][max(0, j - 3)] += temp;}}// 汤A和汤B同时分完,概率为0dp[0][0] /= 2;// 累加所有可能的情况for(int i=1; i<=n; i++)dp[0][0] += dp[0][i];return dp[0][0];}
};

方法三:DFS(自顶向下)

class Solution {public:double soupServings(int n) {// 如果n的值大等于4475,说明汤A肯定会先被分配完if(n >= 4475)   return 1.0;// /25方便计算,ceil向上取整n = ceil((double)n / 25);// 定义动态规划数组dp// dp[i][j]表示汤A和汤B分别剩余i和j时的概率值dp = vector<vector<double>>(n + 1, vector<double>(n + 1));// vector<vector<double>> dp(n + 1, vector<double>(n + 1));// 这种定义的方式会出错,可能因为dp是全局变量return dfs(n, n);}// 记忆化搜索double dfs(int a, int b){if(a <= 0 && b <= 0)    return 0.5; // 汤A和汤B都已经分配完else if(a <= 0) return 1.0; // 汤A分配完else if(b <= 0)    return 0.0; // 汤B分配完// df初始值为0// 所有如果df > 0,则说明已经完成计算if(dp[a][b] > 0)    return dp[a][b];// 计算dfdp[a][b] = 0.25 * (dfs(a - 4, b) + dfs(a - 3, b - 1) + dfs(a - 2, b- 2) + dfs(a - 1, b - 3));return dp[a][b];}
private:vector<vector<double>> dp;
};

心得

  • 今天也没有自己做出来,有想到要用动态规划,但是具体操作还是不了解。
  • 方法一:动态规划(自底向上)
    由于四组容量都是25的倍数,因此四组操作可以简化为(4,0), (3,1), (2,2),(1,3),同时, n 也需要除以 25 。

    • 明确dp[ i ][ j ] 的定义
      假设 dp[i][j] 为汤 A 和汤 B 分别剩下 i ml 和 j ml时候的概率值。

    • 确定状态转移方程
      dp [ i ] [ j ] = 0.25 * (dp [ i - 4 ][ j ] + dp [ i - 3 ][ j - 1 ] + dp [ i - 2 ][ j - 2 ] + dp [ i - 1 ][ j - 3 ])

    • 确定边界条件

      • 当 i > 0, j = 0 时,此时 汤A 不可能先完成分配, 汤A 和 汤B 也不可能同时完成分配,因此概率值 P = 0;
      • 当 i = 0 ,j = 0 时,此时 汤A 和 汤B 同时完成分配,因此概率值 P = 0 + 0.5 * 1 = 0.5;
      • 当 i = 0 ,j > 0时,此时 汤A 已经先完成分配,因此概率值 P = 1.

      综上所述,dp [ i ][ j ]的边界条件就有以上三种。

    • 优化算法
      此时,算法的时间复杂度为O(n2),当 n 特别大的时候,可能会TLE,因此需要进行优化,可以找出 n >= 4475 时,概率值已经接近1,此时可以直接给出答案不必判断。
      那么4475如何得出呢?
      首先我们先计算出 汤A 每次分配的平均期望为 E(A) = (4 + 3 + 2 + 1)/ 4 = 2.5,
      汤B 每次分配的平均期望为 E(B) = (0 + 1 + 2 +3) / 4 = 1.5,因此当n足够大的时候,A肯定会先被分完。由于题目给出了误差值为10-5 ,也就是说当答案 > 0.99999 的时候,可以直接返回1 。
      可以通过运行上面代码寻找到这个值,为4475。

  • 方法二:动态规划(自顶向下)
    自底向上的方法会计算很多无用的值,而自顶向下的动态规划则会避免多余的计算。
    如图,红色块是最终要求得的值,那么需要通过蓝色块得到红色块,灰色块都是无用的值。

  • 从 dp[ n ][ n ]开始,自顶向下依次递推:

    • dp[ n ][ n ],它出现的概率为1,这是既定事实;
    • 确定状态转移方程
      dp[ n ][ n ]进行四次分布后,每种情况的出现概率都为0.25,再一次进行分布也是一样的,因此,得到状态转移方程
      dp[ a - 4 ][ b ] = 0.25 * dp[ a ][ b ]
      dp[ a - 3 ][ b - 1 ] = 0.25 * dp[ a ][ b ]
      dp[ a - 2 ][ b - 2 ] = 0.25 * dp[ a ][ b ]
      dp[ a - 3 ][ b - 1 ] = 0.25 * dp[ a ][ b ]
    • 确定边界条件,计算最终结果
      最终结果 = 汤A 先分完的概率 + 汤A 和 汤B 同时分完的概率

      • 汤A 先分完的概率 = dp[ 0 ][ j ] ,其中 j != 0 且 j <= n;
      • 汤A 和 汤B 同时分完的概率 = dp[ 0 ][ 0 ]
  • 方法三:dfs记忆化搜索(自顶向下)
    这个方法的思路和方法二差不多,也是为了避免无效块的计算。
  • 总结

    • 自底向上的方式(方法一)
      先给最底层(边界)情况赋贡献值,然后向上推出上一层情况对答案的贡献值,以此类推,最终得到答案
    • 自顶向下的方式(方法二,三)
      从最开始的情况计算,向下推出下一层情况的真实发生概率,以此类推,直到所有分支情况到达边界,最终将边界情况的真实概率进行总结计算

参考资料:
[1]优秀题解
[2]深度优先搜索之记忆化dfs

【LeetCode】808.分汤相关推荐

  1. Java实现 LeetCode 808 分汤 (暴力模拟)

    808. 分汤 有 A 和 B 两种类型的汤.一开始每种类型的汤有 N 毫升.有四种分配操作: 提供 100ml 的汤A 和 0ml 的汤B. 提供 75ml 的汤A 和 25ml 的汤B. 提供 5 ...

  2. LeetCode 808. 分汤(动态规划)

    文章目录 1. 题目 2. 解题 1. 题目 有 A 和 B 两种类型的汤.一开始每种类型的汤有 N 毫升.有四种分配操作: 提供 100ml 的汤A 和 0ml 的汤B. 提供 75ml 的汤A 和 ...

  3. 808. 分汤 : 挺有意思的 DP 题

    题目描述 这是 LeetCode 上的 808. 分汤 ,难度为 中等. Tag : 「数学」.「动态规划」.「线性 DP」 有 A 和 B 两种类型 的汤.一开始每种类型的汤有 n 毫升.有四种分配 ...

  4. 逻辑心理测试题:三囚分汤

    一间囚房里关押着两个犯人.每天监狱都会为这间囚房提供一罐汤,让这两个犯人自己来分.起初,这两个 人经常会发生争执,因为他们总是有人认为对方的汤比自己的多.后来他们找到了一个两全其美的办法:一个人分汤, ...

  5. 三个人分汤终极解答,四人分汤,多人分汤终极答案。

    网上其他的基本都是错误的答案呀..思维不慎密..终极正确答案在这里.. 今天看到道题目,感觉很有意思: 一间囚房里关押着两个犯人.每天监狱都会为这间囚房提供一罐汤,让这两个犯人自己来分.起初,这两个人 ...

  6. LeetCode 1103. 分糖果 II

    1. 题目 排排坐,分糖果. 我们买了一些糖果 candies,打算把它们分给排好队的 n = num_people 个小朋友. 给第一个小朋友 1 颗糖果,第二个小朋友 2 颗,依此类推,直到给最后 ...

  7. LeetCode 575. 分糖果(set集合去重)

    1. 题目 给定一个偶数长度的数组,其中不同的数字代表着不同种类的糖果,每一个数字代表一个糖果.你需要把这些糖果平均分给一个弟弟和一个妹妹.返回妹妹可以获得的最大糖果的种类数. 输入: candies ...

  8. Leetcode - 1103. 分糖果 II 排排坐,分糖果。

    分糖果 II 排排坐,分糖果. 我们买了一些糖果 candies,打算把它们分给排好队的 n = num_people 个小朋友. 给第一个小朋友 1 颗糖果,第二个小朋友 2 颗,依此类推,直到给最 ...

  9. LeetCode—Candy(分糖果)—java

    题目描述: There are N children standing in a line. Each child is assigned a rating value. You are giving ...

最新文章

  1. bootstrape实战案例_第二百五十二节,Bootstrap项目实战-首页
  2. python中mode_python中的model模板中的数据类型
  3. eplan模板_EPLAN之3D箱柜清单自动生成
  4. python画饼图-python画饼图的多种方式
  5. QoS策略及通过BGP传播—Vecloud微云
  6. python如何在循环中保存文件_python-如何在for循环中更改为另一行文件
  7. 青年歌手大奖赛_评委会打分
  8. 在FC中如何获取fcdot文件
  9. python如何读取数据保存为新格式_python,初学者应用实例:读取文件中的数据,将将北京时间转换成世界时间,再保存成新的CSV格式文件...
  10. tensorflow学习(一)——有关tensorflow不同层的使用(tf.nn 和tf.layers以及tf.contrib.layers)的简单区别
  11. 基于Maven的S2SH(Struts2+Spring+Hibernate)框架搭建
  12. 无限打开计算机cmd,影子系统无限蓝屏cmd怎么解决
  13. spring 使用aop 缺少依赖包aspectjweaver.jar 和spring-aop.jar报错
  14. 交换机怎么和计算机连接网络打印机,怎么通过地址栏的方式连接网络打印机的方法?...
  15. 利用easyX图形库画迷宫问题的路径
  16. session fixation attack修复方法
  17. 一文弄懂printf函数从用户态到内核态的执行流程
  18. Python 入门经典必背的 18 个程序,学完就入门 Python 了
  19. php面试自我介绍结束语,面试自我介绍的结束语
  20. h3c交换机限制端口访问_H3C交换机限制局域网端口网限方法

热门文章

  1. JAVA数字千分位和小数点的现实(处理金额问题)
  2. 惨痛!这哥们在YouTube直播评论ICO时,自己的ICO被黑了200万刀
  3. 基于element实现文件(资源)上传 - 好用
  4. location.href的用法
  5. playBlastWin.txt
  6. Java判断一个字符串是否有中文
  7. Spring Boot-2-核心注解
  8. 四川大学计算机专业《高级语言程序设计-I》实验合辑
  9. 去哪儿MySQL开发规范
  10. Windows格式化系统盘为数据盘 删除EFI分区和恢复分区