[LeetCode] 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:html

  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:spa

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

 

這道題給了咱們兩種湯,A和B,開始時各給了N毫升的。而後說是有下面四種操做:code

1. 供應100毫升A湯,0毫升B湯。htm

2. 供應75毫升A湯,25毫升B湯。blog

3. 供應50毫升A湯,50毫升B湯。遞歸

4. 供應25毫升A湯,75毫升B湯。ip

咱們選擇每種操做的機率是同樣的,讓咱們返回A湯先供應完的機率加上A湯和B湯同時供應完的一半機率。又給了一個例子來幫助咱們理解。說實話,博主以爲這道題挺讓人費解的,反正博主是沒有啥思路,是直接研究答案的,如今就照着大神們的帖子來說一講吧。

先來看這四種操做,因爲機率相同,因此每一種操做都的有,因此這四種操做能夠想象成迷宮遍歷的周圍四個方向,那麼咱們就能夠用遞歸來作。再看一下題目中給的N的範圍,能夠到10的9次方,而每次湯的消耗最多不過100毫升,因爲純遞歸基本就是暴力搜索,因此咱們須要加上記憶數組memo,來避免重複運算,提升運行的效率。既然用記憶數組,咱們不想佔用太多空間,能夠對工件進行優化。怎麼優化呢,咱們發現湯的供應量都是25的倍數,因此咱們能夠將25毫升看成一份湯的量,因此這四種操做就變成了:

1. 供應4份A湯,0份B湯。

2. 供應3份A湯,1份B湯。

3. 供應2份A湯,2份B湯。

4. 供應1份A湯,3份B湯。

因此咱們的湯份數就是能夠經過除以25來得到,因爲N可能不是25的倍數,會有餘數,可是就算不到一份的量,也算是完成了一個操做,因此咱們能夠直接加上24再除以25就能夠獲得正確的份數。那麼接下來就是調用遞歸了,其實遞歸函數很直接了當,首先判斷若是兩種湯都沒了,那麼返回0.5,由於題目中說了若是兩種湯都供應完了,返回一半的機率;若是A湯沒了,返回1;若是B湯沒了,返回0;若是上面的狀況都沒有進入,說明此時A湯和B湯都有剩餘,因此咱們先查記憶數組memo,若是其大於0,說明當前狀況已經被計算過了,咱們直接返回該值便可。若是沒有的話,咱們就要計算這種狀況的值,經過對四種狀況分別調用遞歸函數中,將返回的機率值累加後除以4便可。這道題還有一個很大的優化,就是當N大過某一個數值的時候,返回的都是1。這裏的4800就是這個閾值返回,這樣的話memo數組的大小就能夠是200x200了,至因而怎麼提早設定的,博主就不知道了,估計是強行試出來的吧,參見代碼以下:

 

解法一:

class Solution {
public:
    double memo[200][200];
    double soupServings(int N) {
        return N >= 4800 ? 1.0 : f((N + 24) / 25, (N + 24) / 25);
    }
    double f(int a, int b) {
        if (a <= 0 && b <= 0) return 0.5;
        if (a <= 0) return 1.0;
        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];
    }
};

 

下面這種解法的思路基本同樣,就是沒有用二維數組,而是用了一個HashMap來保存計算過的值,創建字符串到double到映射,這裏的字符串是由A湯和B湯的剩餘量拼成的,爲了保證惟一性,將兩者的值先轉爲字符串,而後在中間加一個冒號拼在一塊兒。因爲是字符串,因此咱們也不用將毫升數變成份數,直接就原樣保存吧,參見代碼以下:

 

解法二:

class Solution {
public:
    unordered_map<string, double> m;
    double soupServings(int N) {
        return N >= 4800 ? 1.0 : f(N, N);
    }
    double f(int a, int b) {
        if (a <= 0 && b <= 0) return 0.5;
        if (a <= 0) return 1.0;
        if (b <= 0) return 0;
        string spoon = to_string(a) + ":" + to_string(b);
        if (!m.count(spoon)) {
            m[spoon] = 0.25 * (f(a - 100, b) + f(a - 75, b - 25) + f(a - 50, b - 50) + f(a - 25, b - 75));
        }
        return m[spoon];
    }
};

 

參考資料:

https://leetcode.com/problems/soup-servings/discuss/125809/Java-soup-(spoon-included)

https://leetcode.com/problems/soup-servings/discuss/121711/C++JavaPython-When-N-greater-4800-just-return-1

 

LeetCode All in One 題目講解彙總(持續更新中...)

相關文章
相關標籤/搜索