分汤

155 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情

808. 分汤 - 力扣(LeetCode)

有 A 和 B 两种类型 的汤。一开始每种类型的汤有 n 毫升。有四种分配操作:

  1. 提供 100ml汤A0ml汤B
  2. 提供 75ml汤A25ml汤B
  3. 提供 50ml汤A50ml汤B
  4. 提供 25ml汤A75ml汤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 <= 10^9

思路

本题可以使用动态规划 + 记忆化搜索解题。由于四种操作都是25的倍数,因此我们可以将n除以25,并将四种操作变为(4,0)(3,1)(2,2)(1,3),每种操作概率均为0.25。 设汤A汤B当前的量分别为ab,初始化a = b = n,一次操作后ab的量有四种可能:

  • 提供 100ml汤A0ml汤Ba = a - 4b = b
  • 提供 100ml汤A0ml汤Ba = a - 3b = b - 1
  • 提供 50ml汤A50ml汤Ba = a - 2b = b - 2
  • 提供 25ml汤A75ml汤Ba = a - 1b = b - 3

f(a, b)表示汤A 先分配完的概率 +  汤A和汤B 同时分配完的概率 / 2,
f(a, b) = 0.25* (f(a - 4, b) + f(a - 3, b - 1) + f(a - 2, b - 2) + f(a - 1, b - 3))
可以用dp[a][b]存储f(a, b)的值,来避免动态规划过程中存在的重复计算的问题,优化解题过程。

解题

/**
 * @param {number} n
 * @return {number}
 */
var soupServings = function (n) {
  n = Math.ceil(n / 25);
  if (n >= 179) {
    return 1;
  }
  const dp = new Array(n + 1).fill(null).map((_) => new Array(n + 1).fill(0));
  const dfs = (a, b) => {
    if (a <= 0 && b <= 0) {
      return 0.5;
    } else if (a <= 0) {
      return 1;
    } else if (b <= 0) {
      return 0;
    }
    if (dp[a][b] === 0) {
      dp[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];
  };
  return dfs(n, n);
};