leetcode *808. 分汤(2022.11.21)

【题目】*808. 分汤

有 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。返回值在正确答案 1 0 − 5 10^{-5} 105 的范围内将被认为是正确的。

示例 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​​​​​​​

【解题思路1】动态规划

首先,由于四种分配操作都是 25 的倍数,因此我们可以将 n 除以 25(如果有余数,则补 1),并将四种分配操作变为 (4,0),(3,1),(2,2),(1,3),且每种操作的概率均为 0.25。

当 n 较小时,我们可以用动态规划来解决这个问题。设 dp(i,j) 表示汤 A 和汤 B 分别剩下 i 和 j 份时所求的概率值,即汤 A 先分配完的概率 + 汤 A 和汤 B 同时分配完的概率除以 2 。 状态转移方程为:

dp(i,j)=1/4×( dp(i−4,y)+dp(i−3,y−1)+dp(i−2,y−2)+dp(i−1,y−3) )

我们仔细考虑一下边界条件:

扫描二维码关注公众号,回复: 15858174 查看本文章

当满足 i>0,j=0,此时无法再分配,而汤 A 还未分配完成,汤 A 永远无法完成分配,此时 dp(i,j)=0;

当满足 i=0,j=0,此时汤 A 和汤 B 同时分配完的概率为 1.0,汤 A 先分配完的概率为 0,因此 dp(i,j)=1.0×0.5=0.5;

当满足 i=0,j>0,此时无法再分配,汤 A 先分配完的概率为 1.0,汤 B 永远无法完成分配,因此 dp(i,j)=1.0;

因此综上,我们可以得到边界条件如下:

d p ( i , j ) = { 0 , i>0, j=0 0.5 , i=0, j=0 1 , i=0, j>0 ​ dp(i,j)= \begin{cases} 0, & \text{i>0, j=0}\\ 0.5, & \text{i=0, j=0}\\ 1, & \text{i=0, j>0} \end{cases} ​ dp(i,j)=0,0.5,1,i>0, j=0i=0, j=0i=0, j>0

我们可以看到这个动态规划的时间复杂度是 O(n2),即使将 n 除以 25 之后,仍然没法在短时间内得到答案,因此我们需要尝试一些别的思路。 我们可以发现,每次分配操作有 (4,0),(3,1),(2,2),(1,3) 四种,那么在一次分配中,汤 A 平均会被分配的份数期望为 E(A)=(4+3+2+1)×0.25=2.5,汤 B 平均会被分配的份数期望为 E(B)=(0+1+2+3)×0.25=1.5。因此在 n 非常大的时候,汤 A 会有很大的概率比 B 先分配完,汤 A 被先取完的概率应该非常接近 1。事实上,当我们进行实际计算时发现,当 n≥4475 时,所求概率已经大于 0.99999 了(可以通过上面的动态规划方法求出),它和 1 的误差(无论是绝对误差还是相对误差)都小于 1 0 − 5 10^{−5} 105
。实际我们在进行测算时发现:

n=4475,p≈0.999990211307
n=4476,p≈0.999990468596
因此在 n≥179×25n 时,我们只需要输出 1 作为答案即可。在其它的情况下,我们使用动态规划计算出答案。

class Solution {
    
    
    public double soupServings(int n) {
    
    
        n = (int) Math.ceil((double) n / 25);
        if (n >= 179) {
    
    
            return 1.0;
        }
        double[][] dp = new double[n + 1][n + 1];
        dp[0][0] = 0.5;
        for (int i = 1; i <= n; i++) {
    
    
            dp[0][i] = 1.0;
        }
        for (int i = 1; i <= n; i++) {
    
    
            for (int j = 1; j <= n; j++) {
    
    
                dp[i][j] = (dp[Math.max(0, i - 4)][j] + dp[Math.max(0, i - 3)][Math.max(0, j - 1)] + dp[Math.max(0, i - 2)][Math.max(0, j - 2)] + dp[Math.max(0, i - 1)][Math.max(0, j - 3)]) / 4.0;
            }
        }
        return dp[n][n];
    }
}

【解题思路2】DFS

class Solution {
    
    
    private double[][] memo;

    public double soupServings(int n) {
    
    
        n = (int) Math.ceil((double) n / 25);
        if (n >= 179) {
    
    
            return 1.0;
        }
        memo = new double[n + 1][n + 1];
        return dfs(n, n);
    }

    public double dfs(int a, int b) {
    
    
        if (a <= 0 && b <= 0) {
    
    
            return 0.5;
        } else if (a <= 0) {
    
    
            return 1;
        } else if (b <= 0) {
    
    
            return 0;
        }
        if (memo[a][b] == 0) {
    
    
            memo[a][b] = 0.25 * (dfs(a - 4, b) + dfs(a - 3, b - 1) + dfs(a - 2, b - 2) + dfs(a - 1, b - 3));
        }
        return memo[a][b];
    }
}

猜你喜欢

转载自blog.csdn.net/XunCiy/article/details/127956729