[Leetcode] 808. Soup Servings 解题报告

题目

There are two types of soup: type A and type B. Initially we have N ml of each type of soup. There are four kinds of operations:

  1. Serve 100 ml of soup A and 0 ml of soup B
  2. Serve 75 ml of soup A and 25 ml of soup B
  3. Serve 50 ml of soup A and 50 ml of soup B
  4. Serve 25 ml of soup A and 75 ml of soup B

When we serve some soup, we give it to someone and we no longer have it.  Each turn, we will choose from the four operations with equal probability 0.25. If the remaining volume of soup is not enough to complete the operation, we will serve as much as we can.  We stop once we no longer have some quantity of both types of soup.

Note that we do not have the operation where all 100 ml's of soup B are used first.  

Return the probability that soup A will be empty first, plus half the probability that A and B become empty at the same time.

 Example:

Input: N = 50
Output: 0.625
Explanation: 
If we choose the first two operations, A will become empty first. For the third operation, A and B will become empty at the same time. For the fourth operation, B will become empty first. So the total probability of A becoming empty first plus half the probability that A and B become empty at the same time, is 0.25 * (1 + 1 + 0.5 + 0) = 0.625.

Notes:

  • 0 <= N <= 10^9
  • Answers within 10^-6 of the true value will be accepted as correct.

思路

自己当初写的代码的思路是对的,但是细节实现上没考虑周全。这里还是参考了网上代码的总结:

我们在这里采用的方法严格来讲是DFS + memorization,也就是需要计算一个子问题的时候,我们首先在表格中查找,看看原来有没有被计算过,如果被计算过,则直接返回结果,否则就再重新计算,并将结果保存在表格中。这样的好处是没必要计算每个子问题,只计算递归过程中用到的子问题。如果我们定义f(a, b)表示有a毫升的A和b毫升的B时符合条件的概率,那么容易知道递推公式就是:f(a, b) = 0.25 * (f(a - 4, b) + f(a - 3, b - 1) + f(a - 2, b - 2) + f(a - 1, b - 3)),其中平凡条件是:

当a < 0 && b < 0时,f(a, b) = 0.5,表示A和B同时用完;

当a <= 0 && b > 0时,f(a, b) = 1.0,表示A先用完;

当a > 0 && b<= 0时,f(a, b) = 0.0,表示B先用完。

所以当遇到这三种情况的时候,我们直接返回对应的平凡值;否则就首先查表,看看原来有没有计算过,如果已经计算过了,就直接返回;否则才开始按照递推公式计算。

这道题目我觉得在实现上有两点需要特别注意:

1)如果A或者B不足25ml,但是又大于0ml,那么我们需要把它当做完整的25ml来对待。另外,由于A和B serve的最小单位是25ml,所以我们在f(a, b)中约定a和b是25ml的倍数,具体在实现中,我们需要首先对n做n = ceil(N / 25.0)的处理。

2)题目中给出N的范围是[0, 10^9],这是一个特别大的数字了。另外又提到当我们返回的结果与真实误差小于10^6的时候,就算正确。直觉告诉我们,当N趋向于无穷大时,A先被serve完以及A和B同时被serve完的概率会无限接近于1。经过严格计算我们知道当N >= 4800之后,返回的概率值与1的差距就小于10^6了。所以当N >= 4800的时候,我们就直接返回1。如果不这样做的话,就会导致memo需要开辟的内容特别大,引起MLE。

代码

class Solution {
public:
    double soupServings(int N) {
        int n = ceil(N / 25.0);
        return N >= 4800 ?  1.0 : f(n, n);
    }
private:
    double f(int a, int b) {
        if (a <= 0 && b <= 0) {
            return 0.5;
        }
        if (a <= 0) {
            return 1;
        }
        if (b <= 0) {
            return 0;
        }
        if (memo[a][b] > 0) {
            return memo[a][b];
        }
        memo[a][b] = 0.25 * (f(a - 4, b) + f(a - 3, b - 1) + f(a - 2, b - 2) + f(a - 1, b - 3));
        return memo[a][b];
    }
    double memo[200][200];
};


你可能感兴趣的:(IT公司面试习题)