LeetCode 837. 新21點 | Python

837. 新21點


題目來源:力扣(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

  • 0 <= K <= N <= 10000
  • 1 <= W <= 10000
  • 若是答案與正確答案的偏差不超過 10^-5,則該答案將被視爲正確答案經過。
  • 此問題的判斷限制時間已經減小。

解題思路


思路:動態規劃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]

實現結果


實現結果

總結


  • 題目中使用的思路是動態規劃,這裏主要的難點在於推導狀態轉移方程。
  • 根據題意,推導的方向是由後面的狀況往前進行推導。先處理最後一次抽取數字的狀況,而後再往類推。
  • 遊戲是否能贏,跟下一次開始抽取前的分數是關聯的。當前抽取能贏的機率實際上是由後面抽取數字後累計分數是否超過 N 的機率總和。根據這個就可以求得狀態轉移方程。
  • 由於最初的轉移方程會超時,那麼將其進行優化。根據相鄰差分,簡化狀態轉移方程。

若是以爲文章寫得還能夠,歡迎關注。公衆號《書所集錄》同步更新,一樣歡迎關注。
相關文章
相關標籤/搜索