題目描述
愛麗絲參與一個大致基於紙牌遊戲 “21點” 規則的遊戲,描述如下:
愛麗絲以0
分開始,並在她的得分少於 K
分時抽取數字。 抽取時,她從[1, W]
的範圍中隨機獲得一個整數作爲分數進行累計,其中W
是整數。 每次抽取都是獨立的,其結果具有相同的概率。
當愛麗絲獲得不少於K
分時,她就停止抽取數字。 愛麗絲的分數不超過N
的概率是多少?
示例 1:
輸入:N = 10, K = 1, W = 10
輸出:1.00000
說明:愛麗絲得到一張卡,然後停止。
示例 2:
輸入:N = 6, K = 1, W = 10
輸出:0.60000
說明:愛麗絲得到一張卡,然後停止。
在 W = 10 的 6 種可能下,她的得分不超過 N = 6 分。
示例 3:
輸入:N = 21, K = 17, W = 10
輸出:0.73278
提示:
0 <= K <= N <= 10000
1 <= W <= 10000
如果答案與正確答案的誤差不超過 10^-5,則該答案將被視爲正確答案通過。
此問題的判斷限制時間已經減少。
解題思路
本題利用動態規劃的思想,令dp[i]
表示當手裏的數字爲i時獲勝的概率,本題要求dp[0]
。
因爲手中牌最大爲K+W-1
(手中牌爲K-1
,又抽中了W
),因此初始化一個長度爲K+W
的數組。
- 當手中的牌當
N >= i >= K
時,遊戲結束,獲得勝利(返回1
)。當i>N
時,遊戲失敗(返回0
)。 - 當手中的牌
i <= K-1
時,dp[i] = (dp[i+1]+...+dp[i+W]) / W
。
由於利用循環求和dp[i+1]+...+dp[i+W]
時會超出時間限制,因此我們利用窗口效應,設一個變量window
來記錄當前的dp[i+1]+...+dp[i+W]
。i
每向前移動一格,窗口也會跟着移動一格。如下圖所示:
上圖參考:
https://leetcode-cn.com/problems/new-21-game/solution/
具體實現細節詳見僞代碼。
完整代碼
class Solution {
public:
double new21Game(int N, int K, int W) {
//如果K爲0,最後肯定可以成功。
if(K == 0){
return 1.0;
}
//利用動態規劃的思想,dp[i]表示當手裏的數字爲i時獲勝的概率,本題要求dp[0]。
//手中牌最大爲K+W-1,因此初始化一個長度爲K+W的數組。
double dp[K+W];
/*for(int i =0;i<K+W-1;i++){
dp[i] = 0;
}*/
double window = 0.0;
//當N>=i>=K時,遊戲結束,獲得勝利。當i>N時,遊戲失敗。
for(int i = K;i<K+W;i++){
dp[i] = (i > N) ? 0 : 1.0;
window += dp[i];//利用window值記錄這一總和(滑動窗口原理)
}
//當i<=K-1時,dp[i] = (dp[i+1]+...+dp[i+W])/W
for(int i = K-1;i>=0;i--){
dp[i] = window/W;
window += dp[i];
window -= dp[i+W];
}
return dp[0];
}
};
性能結果
個人感悟
做了一些動態規劃的題,個人感覺確實是狀態轉移方程最難寫。
有些時候需要求dp[i]
,比如No.194 LeetCode題目 “打家劫舍”。
有些時候需要求dp[0]
,比如本題。
這兩種思路主要是要考慮從前往後計算,還是從後往前計算。
這道題就是要從後往前計算,因爲後一狀態決定了前一狀態。
同時dp[i]
這個物理量如何解釋也是要思考的問題之一,之後遇到動態規劃還要多多練習。