[LeetCode] 837. New 21 Game 新二十一點遊戲

 

Alice plays the following game, loosely based on the card game "21".html

Alice starts with 0 points, and draws numbers while she has less than K points.  During each draw, she gains an integer number of points randomly from the range [1, W], where W is an integer.  Each draw is independent and the outcomes have equal probabilities.git

Alice stops drawing numbers when she gets K or more points.  What is the probability that she has N or less points?github

Example 1:數組

Input: N = 10, K = 1, W = 10
Output: 1.00000
Explanation:  Alice gets a single card, then stops.

Example 2:less

Input: N = 6, K = 1, W = 10
Output: 0.60000
Explanation:  Alice gets a single card, then stops.
In 6 out of W = 10 possibilities, she is at or below N = 6 points.

Example 3:dom

Input: N = 21, K = 17, W = 10
Output: 0.73278

Note:post

  1. 0 <= K <= N <= 10000
  2. 1 <= W <= 10000
  3. Answers will be accepted as correct if they are within 10^-5 of the correct answer.
  4. The judging time limit has been reduced for this question.
 

這道題就是賭桌上經典的 21 點遊戲了,想起了當年實習的遊輪活動,就有21點遊戲的賭桌。我當時還納悶爲啥莊家到了 17 點之後就再也不要牌了,原來裏面大有學問啊,由於再多拿牌,增大了爆的機率,而若是小於 17 就不拿牌的話,會增大玩家贏的機率,估計是通過精心計算,用這個閾值莊家贏的機率最大吧,想着當時莊家每拿一張牌,你們都一塊兒在喊「爆,爆,爆。。」的情景,仍是以爲很是搞笑。但當時有一位同期實習的大神,能夠根據分析檯面上已經出現的牌,來推出最合理的策略,由於莊家的規則是不變的,只要過了 17 就堅定不拿牌,可是大神卻能夠根據已出現的牌來制定本身的最優策略,常常能贏莊家。據大神稱他去賭場常常都能贏上個小二百刀,給跪了有木有?!this

好,來解題吧。這道題說的是有 [1, W] 範圍內的牌,問咱們當拿到很多於K點的時候就中止摸牌,最終點數能不超過的N點的機率。那麼咱們先來分析下,拿到 [1, W] 範圍內的任意一張牌的機率是 1/W,由於是隨機取的,因此拿到任意張牌的機率都是相等的。那麼點數大於W的時候,機率怎麼算呢,好比 W = 10, 咱們拿到15點的機率是多少呢?這時候確定不止拿一張牌了,那麼咱們分析最後一張牌,能夠取1到 10,那麼能拿到 15 點就有十種狀況,以前共拿5點且最後一張拿10,以前共拿6點且最後一張拿9,以前拿共7點且最後一張拿8,...,以前共拿 14 點且最後一張拿1。那麼拿 15 點的機率就是把這十種的機率都加起來。這道題給的假設是每次取牌都是等機率的,無論何時拿到 [1, 10] 內的任意張牌的機率都是十分之一,可是現實狀況確定不是這樣的,已經取出了的牌,不會放回了,因此現實狀況要更加複雜。不用管它,反正咱們拿最後一張牌的機率都是 1/W,因爲是‘且’的關係,因此是機率相乘,能夠將 1/W 提取出來,那麼對於拿到x點的機率就能夠概括出下面的等式:url

P(x) = 1/W * (P(x-1) + P(x-2) + P(x-W))spa

       = 1/W * sumP(x-W, x-1)

這裏的x是有範圍限制的,必須在 [W, K] 之間,由於小於等於W的點數機率都是 1/W,而大於等於K的時候,就不會再拿牌了。如今回過頭來看看這道題要咱們求什麼,要求的是拿到很多於K點的時候就中止摸牌,最終點數能不超過的N點的機率,即 P(<=N | >= K)。那麼如今咱們就要引入強大的條件機率公式了,傳說中的貝葉斯公式就是由其推導出來的:

P(A | B) = P(AB) / P(B)

意思就是在事件B發生的條件下發生事件A的機率,等於事件A和B同時發生的機率除以事件B單獨發生的機率。那麼帶入本題的環境,就能夠獲得下列等式:

P(<=N | >=K) = P(<=N && >=K) / P(>=K)

就是說拿到不小於K點的前提下,還能不超過N點的機率,等於拿到不小於K點且不超過N點的機率除以拿到不小於K點的機率。這樣,咱們只要分別求出 P(<=N && >=K) 和 P(>=K) 就能夠了:

P(<=N && >=K) = P(K) + P(K+1) + ... + P(N) = sumP(K, N)

P(>=K) = sumP(K, +∞) = sumP(K, K+W-1)

須要注意的是,一旦大於等於 K+W了,那麼機率就爲0了,因此邊界就從正無窮降到 K+W-1 了。既然說到了邊界,那麼就來處理一下 corner case 吧,當 K=0 時,因爲題目中說當前點數大於等於K,不能摸牌,那麼一開始就不能摸牌了,而 K <= N,因此永遠不會超過N,機率返回1。還有就是當 N >= K+W 的時候,當咱們大於等於K的時候,不能摸牌,此時不會超過N。當恰好爲 K-1 的時候,此時還有一次摸牌機會,但最大也就摸個W,總共爲 K-1+W,仍是小於N,因此返回機率爲1。

根據上面的條件機率公式推導,P(>=K) 的邊界降到了 K+W-1, 因此咱們只要更新到這個邊界就都用了,由於 P(<=N && >=K) 的範圍是 [K, N],而 N 是要小於 K+W 的。咱們新建一個大小爲 K+W 的一維數組 sums,其中 sum[i] 表示得到範圍 [0, i] 內的點數的機率綜合,初始化 sum[0] 爲 1.0。下面來推導狀態轉移方程吧 ,一般來講,咱們要更新 sum[i],那麼只要知道了 sum[i-1],就只要算出 P[i],就好了,由於 sum[i] = sum[i-1] + P[i]。但這道題的更新其實比較複雜,要考慮兩個關鍵的位置,K和W,咱們仍是用經典的21點遊戲來舉例說明吧,N=21, K=17, W=10。先來講一下當點數不超過 10 的更新方法,這個其實比較簡單,好比拿到七點的機率 P[7],根據咱們上面對於 P(x) 的求法,咱們知道能夠拆分爲下列多種狀況:先拿到六點的機率 (P[6]) 乘以再拿一個1點的機率 (1/W),先拿到五點的機率 (P[5]) 乘以再拿一個2點的機率 (1/W),...,先拿到一點的機率 (P[1]) 乘以再拿一個六點的機率 (1/W),直接拿個七點的機率 (1/W),那麼通通加起來,就是:

P[7] = 1/W * (P[6] + p[5] + ... + P[1] + P[0]) = 1/W * sum[6]

那麼概括一下,對於 i <= W 的狀況下:

P[i] = 1/W * sum[i-1]

sum[i] = sum[i-1] + P[i] = sum[i-1] + sum[i-1] / W     (when i <= W)

那麼當 i > W 的時候,狀況是不同的,好比要求獲得 15 點的機率 P[15],那麼仍是根據上面求 P(x) 的方法,拆分爲下面多種狀況:先拿到 14 點的機率 (P[14]) 乘以再拿一個1點的機率 (1/W),先拿到 13 點的機率 (P[13]) 乘以再拿一個2點的機率 (1/W),...,先拿到五點的機率 (P[5]) 乘以再拿一個 10 點的機率 (1/W),那麼通通加起來就是:

P[15] = 1/W * (P[14] + P[13] + ... + P[5]) = 1/W * (sum[14] - sum[4])

那麼概括一下,對於 i > W 的狀況下:

P[i] = 1/W * (sum[i-1] - sum[i-W-1])

sum[i] = sum[i-1] + P[i] = sum[i-1] + (sum[i-1] - sum[i-W-1]) / W     (when i > W)

到這裏,你覺得就大功告成了嗎?圖樣圖森破,嘛噠得斯。還有一個K呢,更新K之內的P值,和更新大於K的P值是稍有不一樣的,好比當 K=17 時,咱們要更新 P[15],那麼跟上面分析的同樣,同時還得考慮W的狀況,概括一下:

P[i] = 1/W * sum[i-1]     (when i <= K && i <= W)

P[i] = 1/W * (sum[i-1] - sum[i-W-1])    (when i <= K && i > W)

可是對於大於K的值,好比 P[20] 的更新方法就有所不一樣了,爲啥呢?這要分析 20 點是怎麼得來的,因爲超過了 17 點就不能再摸牌了,因此 20 點只能由下列狀況組成:先拿到 16 點的機率 (P[16]) 再拿到一個4點的機率 (1/W),先拿到 15 點的機率 (P[15]) 再拿到一個5點的機率 (1/W),...,先拿到 10 點的機率 (P[10]) 再拿到一個 10 點的機率 (1/W),那麼通通加起來就是:

P[20] = 1/W * (P[16] + P[15] + P[14] + ... + P[10]) = 1/W * (sum[16] - sum[9])

那麼咱們概括一下,就有:

P[i] = 1/W * sum[K-1]     (when i > K && i <= W)

P[i] = 1/W * (sum[K-1] - sum[i-W-1])    (when i > K && i > W)

講到這裏,是否是頭暈又眼花,哈哈,博主也快繞暈了,最重要的四個式子已經加粗顯示了,K和W的大小關係實際上是不知道的,不過咱們能夠把兩者揉在一塊兒,咱們每次使用 i-1 和 K-1 中的較小值來算 P[i] 便可,這樣就完美把K融到了W的分類狀況中,當 sum 數組計算完成以後,咱們就直接按照上面的條件機率公式來算 P(<=N | >=K) = P(<=N && >=K) / P(>=K) = sumP(K, N) / sumP(K, K+W-1) 就好了,寫的累s博主了,聽個《青鳥》緩解一下吧,跟博主一塊兒唱~阿歐伊,阿歐伊,阿弄嗖啦~

 

解法一:

class Solution {
public:
    double new21Game(int N, int K, int W) {
        if (K == 0 || N >= K + W) return 1.0;
        vector<double> sum(K + W);
        sum[0] = 1.0;
        for (int i = 1; i < K + W; ++i) {
            int t = min(i - 1, K - 1);
            if (i <= W) sum[i] = sum[i - 1] + sum[t] / W;
            else sum[i] = sum[i - 1] + (sum[t] - sum[i - W - 1]) / W;
        }
        return (sum[N] - sum[K - 1]) / (sum[K + W - 1] - sum[K - 1]);
    }
};

 

下面這種解法跟上面的解法沒有啥本質的區別,這裏的 dp 數組跟上面的 sum 數組表達的意思是徹底同樣的,dp[i] 表示得到範圍 [0, i] 內的點數的機率綜合,初始化 dp[0] 爲 1.0。但願博主在上面已經解釋清楚了,咱們能夠看到,這裏並無將K融合到W的分類中,而是多加了 (K, i] 區間的部分,因此當 i > K 時就要將這部分多加的減去,從而符合題意。還有一點讓博主驚奇的地方是,這道題的條件機率和聯合機率是相同的,根據以前的條件機率公式:

P(<=N | >=K) = P(<=N && >=K) / P(>=K)

就是說拿到不小於K點的前提下,還能不超過N點的機率,等於拿到不小於K點且不超過N點的機率除以拿到不小於K點的機率。可是實際上這道題 P(<=N | >=K) = P(<=N && >=K),即拿到不小於K點的前提下,還能不超過N點的機率,等於拿到不小於K點且不超過N點的機率。那麼就是說拿到不小於K點的機率的老是爲1,想一想也是阿,只有在拿到很多K點的時候才中止摸牌,這樣確定點數很多於K點阿,單獨計算這個機率簡直是畫蛇添足啊,參見代碼以下:

 

解法二:

class Solution {
public:
    double new21Game(int N, int K, int W) {
        if (K == 0 || N >= K + W) return 1.0;
        vector<double> dp(K + W);
        dp[0] = 1.0;
        for (int i = 1; i < K + W; ++i) {
            dp[i] = dp[i - 1];
            if (i <= W) dp[i] += dp[i - 1] / W;
            else dp[i] += (dp[i - 1] - dp[i - W - 1]) / W;
            if (i > K) dp[i] -= (dp[i - 1] - dp[K - 1]) / W;
        }
        return dp[N] - dp[K - 1];
    }
};

 

下面這種解法仍是大同小異,吃透了解法一的講解,看這些變形基本都比較好理解。這裏的 dp 數組意義跟上面的同樣,可是並無初始化大小爲 K+W,而是隻初始化爲了 N+1,爲啥呢,根據解法二的講解,咱們知道了這道題的條件機率和聯合機率是相等的,因此只要求出 P(<=N && >=K),即 dp[N] - dp[K-1],而這題不是更新完整個dp數組後再求聯合機率,而是在更新的過程當中就累加到告終果 res,當 i >= K 的時候,正好能夠將機率加入到結果 res,並且此時不用再累加 sumW,這裏的 sumW 是保存的到目前爲止的機率和,至關於以前的 dp[i-1],還須要判斷的是當 i >= W 時,要減去多加的機率 dp[i-W],參見代碼以下:

 

解法三:

class Solution {
public:
    double new21Game(int N, int K, int W) {
        if (K == 0 || N >= K + W) return 1.0;
        vector<double> dp(N + 1);
        dp[0] = 1.0;
        double sumW = 1.0, res = 0.0;
        for (int i = 1; i <= N; ++i) {
            dp[i] = sumW / W;
            if (i < K) sumW += dp[i];
            else res += dp[i];
            if (i - W >= 0) sumW -= dp[i - W];
        }
        return res;
    }
};

 

Github 同步地址:

https://github.com/grandyang/leetcode/issues/837

 

參考資料:

https://leetcode.com/problems/new-21-game/

https://leetcode.com/problems/new-21-game/discuss/132334/One-Pass-DP-O(N)

https://leetcode.com/problems/new-21-game/discuss/132478/C%2B%2B-12ms-O(K%2BW)-solution-with-explanation

https://leetcode.com/problems/new-21-game/discuss/132358/Java-O(K-%2B-W)-DP-solution-with-explanation

 

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

相關文章
相關標籤/搜索