題目來源:力扣(LeetCode)https://leetcode-cn.com/problems/new-21-gamepython
愛麗絲參與一個大體基於紙牌遊戲 「21點」 規則的遊戲,描述以下:bash
愛麗絲以 0 分開始,並在她的得分少於 K 分時抽取數字。 抽取時,她從 [1, W] 的範圍中隨機得到一個整數做爲分數進行累計,其中 W 是整數。 每次抽取都是獨立的,其結果具備相同的機率。優化
當愛麗絲得到很多於 K 分時,她就中止抽取數字。 愛麗絲的分數不超過 N 的機率是多少?spa
示例 1:code
輸入:N = 10, K = 1, W = 10 輸出:1.00000 說明:愛麗絲獲得一張卡,而後中止。
示例 2:blog
輸入:N = 6, K = 1, W = 10 輸出:0.60000 說明:愛麗絲獲得一張卡,而後中止。 在 W = 10 的 6 種可能下,她的得分不超過 N = 6 分。
示例 3:遊戲
輸入:N = 21, K = 17, W = 10 輸出:0.73278
提示:leetcode
思路:動態規劃rem
題目中,提供了三個變量。N, K, W,這裏先簡單解釋下三者分別是什麼?get
N:這裏至關於一個界限,題目中要求的就是最終抽取數字和與 N 的比較斷定輸贏
K:這裏表示一個可繼續抽取數字的條件
W:數字面值,也就是抽取的數字,是在 1 ~ W 的範圍內
如今先看示例 1:
N = 10, K = 1, W = 10
由於愛麗絲是以 0 開局的,此時 0 < K = 1,那麼她如今能夠繼續抽數字,而數字面值範圍在 [1, 10]。要求抽取數字和小於 N(N=10)的機率?這裏能比較明顯的看出來,機率是 1。由於面值在 [1, 10],不管抽取那個數字,都會大於 K,符合不在抽取的條件,並且最終的和也會落在 [1, 10] 之間,這裏的結果確定小於等於 N。因此機率是 1。
再看示例 2:
N = 6, K = 1, W = 10
這裏改動的是 N 值。抽取的情形也如示例 1,在面值 [1, 10] 的範圍內抽取數字,不管抽到是哪一個數字都不能再次抽取,由於和大於 K,最終的和一樣落在 [1, 10] 之間。可是這裏的 N 值已經變爲 6,這裏抽取最終數字和只有落在 [1, 6] 這部分才符合不超過 N,這部分佔整體部分 60%,因此機率爲 0.6。
而示例 3,則比較複雜。也是咱們主要須要分析的一種狀況。
咱們能夠看到,前面例子 1, 2 中,愛麗絲以 0 開始抽取數字,以後與抽取的數字進行累加,而後跟 K 跟 W 比較,去肯定是否可再次抽取,以及是否不超過 N?
其實這裏能夠看到,愛麗絲可以贏的機率實際上是跟下一輪開始前的分數有關的。
如今令 dp[i]
表示從分數爲 i 的狀況下開始進行抽取數字可以贏的機率,那麼最終要求的就是 dp[0]
的結果。
如今要求出狀態轉移方程,先看題目中所給的一些條件。
當分數超過 K 時,這個時候中止抽數字進行結算,當分數不超過 N 時則斷定爲勝利,不然失敗。
如今,先看下最後一次可以抽取的最大分數。由於超過 K,不可以進行再次抽取。那麼想要再次進行抽取,此時容許的最大分數便是 K - 1。那麼再次抽取中可抽取最大的數字是 W,因此最大分數便是 K - 1 + W。
也就是說當 $K \leq i \leq min(N, K+W-1)$ 時,這個時候 dp[i]=1
,而 $i > min(N,K+W-1)$ 時,dp[i]=0
。
那麼如今要求當 $0\leq i < K$ 時 dp[i] 的值,應該如何求?
其實當 $0\leq i < K$ 時,再進行抽取的時候,此時的機率是由後面抽取數字後累計分數是否超過 N 的機率總和。而前面題目說了,在 [1, W] 數字之間進行抽取的機率是相等的。那麼狀態轉移方程則以下:
$$ dp[i] = \frac{dp[i+1]+dp[i+2]+...+dp[i+W]}{W} $$
雖然狀態轉移方程已經求出來了,可是會發現將轉移方程代入代碼中會超時。下面進行優化:
在這裏,咱們先看 dp[i+1] 是怎樣的?根據前面的狀態轉移方程:
$$ dp[i+1] = \frac{dp[i+2]+dp[i+3]+...+dp[i+W+1]}{W} $$
不過這個時候 $0 \leq i < K-1$。
能夠看到,其實 dp[i] 跟 dp[i+1] 中間有一大部分是相同的,那麼 dp[i] 的值,可由 dp[i+1] 獲得:
$$ dp[i]-dp[i+1] =\frac{dp[i+1]-dp[i+W+1]}{W} $$
此時:
$$ dp[i] = dp[i+1] - \frac{dp[i+W+1]- dp[i+1]}{W} $$
在這裏,$i = K - 1$,不適用於上面的公式,那麼將其代入最開始的轉移方程中:
$$ dp[K-1] = \frac{dp[K]+dp[K+1]+dp[K+W-1]}{W} $$
咱們前面有個 $dp[i]=1$ 的條件,便是當 i 的取值範圍在 $[K, min(N, K+W-1)]$。
此時,i 爲 K-1 時再次抽取數字有可能贏的部分就落在 $min(N, K+W-1) - K + 1$ 次抽取數字上,那麼結果爲:
$$ dp[K-1]=\frac{min(N, K+W-1)-K+1}{W}=\frac{min(N-K+1, W)}{W} $$
而其餘的值則有新的轉移方程進行求得。
具體的代碼以下。
class Solution: def new21Game(self, N: int, K: int, W: int) -> float: dp = [0.0] * (K+W) # 先對機率爲 1.0 的狀況進行處理 # 就是 i 落在 [K, min(N, K+W-1)] 這部分 for i in range(K, min(N, K+W-1) + 1): dp[i] = 1.0 # 這裏先計算 K - 1 的狀況 # 將推導的結果公式放在這裏 dp[K-1] = float(min(N-K+1, W) / W) # 這裏開始從 K-2 計算到 dp[0] 的值 for i in range(K-2, -1, -1): dp[i] = dp[i+1] - (dp[i+W+1]-dp[i+1]) / W return dp[0]
若是以爲文章寫得還能夠,歡迎關注。公衆號《書所集錄》同步更新,一樣歡迎關注。