808 分汤(递归、二维动态规划)

265 篇文章 5 订阅
96 篇文章 2 订阅

1. 问题描述:

有 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。

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

注释:

  • 0 <= N <= 10^9
  • 返回值在 10^-6 的范围将被认为是正确的。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/soup-servings

2. 思路分析:

① 分析题目可以知道每一次对于当前存在A类型的汤x毫升、B类型的汤y毫升有四种可能选择的方案,如果选择其中一种方案那么选择这种方案的概率就为1 / 4,而我们需要计算的是:汤A先分配完的概率 + 汤A和汤B同时分配完的概率 / 2,所以需要将选择四种方案中的每一种方案中汤A先分配完的概率 + 汤A和汤B同时分配完的概率 / 2的结果累加起来,因为存在四种可能选择的方案,所以最容易想到的是使用递归的方法进行解决,在递归的方法体中对应着四个递归的方法。从题目中可以知道分配的操作中汤的体积都为25的倍数,所以为了节省空间,将分配方案中A/B类型的汤各自除以25,并且对一开始汤的类型的体积也除以25(向上取整),当A <= 0并且B <= 0时那么表示AB同时分完了,返回0.5,与题目中汤A和汤B同时分配完的概率 / 2对应,当A <= 0时表明A先分完这个时候返回1.0,当B <= 0时那么应该表明B先分完,如果AB还没有分完继续递归下去,并且从控制台输入的N大概是10000的时候好像概率与1的差距小于10 ^-6(通过提交代码看哪一个结果与1的差距在10^-16以内即可)。因为涉及到四种方案的选择,为了避免重复性的递归,使用了字典来记录已经求解过的值,也即记忆型的递归,因为递归方法中只涉及到两个动态变化的参数,所以只需要记录AB两个参数即可,可以将AB作为一个元组元素存储到字典中即可,当当前这一层的递归完成之后那么将递归的结果存储到对应的字典中并且进行返回即可

② 由①可以知道其实这道题目为组合问题,我们可以根据①中的分析将其优化为二维动态规划问题。可以声明一个长度为n的二维列表(n = N // 25向上取整),dp[i][j]表示当前A类型的汤剩余i毫升,B类型的汤剩余j毫升的情况下的汤A先分配完的概率 + 汤A和汤B同时分配完的概率 / 2的概率,使用两层循环进行递推即可,其实与递归的时候是类似的,递归是往数据规模小的进行,由小的数据规模返回的结果进一步得到大的数据规模的结果,对于动态规划方法那么就可以使用可以由小的数据数据规模推导出大的数据规模的dp列表值。

3. 代码如下:

递归:

import collections


class Solution:
    # 记忆型的递归: 使用字典记录求解过的值
    def dfs(self, A: int, B: int, rec):
        if (A, B) in rec: return rec[(A, B)]
        if A <= 0 and B <= 0:
            # AB同时分完那么概率为1 / 2(与题目的测试用例的计算方式是一致的)
            return 0.5
        elif A <= 0:
            return 1.0
        elif B <= 0:
            return 0.0
        else:
            rec[(A, B)] = 0.25 * (
                        self.dfs(A - 4, B, rec) + self.dfs(A - 3, B - 1, rec) + self.dfs(A - 2, B - 2, rec) + self.dfs(A - 1, B - 3, rec))
            return rec[(A, B)]

    def soupServings(self, N: int) -> float:
        if N >= 10000: return 1.0
        rec = collections.defaultdict(tuple)
        N = N // 25 if N % 25 == 0 else N // 25 + 1
        return self.dfs(N, N, rec)

动态规划:

class Solution:
    # 转换为二维动态规划解决即可
    def soupServings(self, N: int) -> float:
        if N == 0: return 0.5
        if N >= 10000: return 1.0
        choose = [(4, 0), (3, 1), (2, 2), (1, 3)]
        N = N // 25 if N % 25 == 0 else N // 25 + 1
        dp = [[0] * (N + 1) for i in range(N + 1)]
        dp[0][0] = 0.5
        for i in range(1, N + 1):
            dp[i][0] = 0.0
            dp[0][i] = 1.0
        for i in range(1, N + 1):
            for j in range(1, N + 1):
                for k in range(4):
                    x = max(0, i - choose[k][0])
                    y = max(0, j - choose[k][1])
                    dp[i][j] += dp[x][y]
                dp[i][j] *= 0.25
        return dp[N][N]

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值