我的個人微信公衆號:Microstrong
微信公衆號ID:MicrostrongAI
微信公衆號介紹:Microstrong(小強)同學主要研究機器學習、深度學習、計算機視覺、智能對話系統相關內容,分享在學習過程中的讀書筆記!期待您的關注,歡迎一起學習交流進步!
知乎主頁:https://www.zhihu.com/people/MicrostrongAI/activities
題目鏈接
https://leetcode-cn.com/problems/nge-tou-zi-de-dian-shu-lcof/
題目描述
解題思路
(1)遞歸解法
import math
from typing import List
class Solution:
# 定義骰子最大點數
g_maxValue = 6
# 方法一:基於遞歸求骰子點數,時間效率不夠高
def twoSum(self, n: int) -> List[float]:
if n < 1:
return []
# 定義n個骰子的最大點數
maxSum = n * self.g_maxValue
# 所有可能的值出現的次數保存在列表中
pProbabilities = [0] * (maxSum - n + 1)
# 遞歸中的第一個骰子有6種情況
for i in range(1, self.g_maxValue + 1):
# 遞歸剩餘的(n-1)個骰子
self.getProbability(n, n, i, pProbabilities)
# n個骰子的所有點數的排列數爲6的n次方
total = math.pow(self.g_maxValue, n)
res = list(map(lambda x: x / total, pProbabilities))
return list(map(lambda x: round(x, 5), res))
def getProbability(self, original, current, sum, pProbabilities):
'''
:param original: 表示n個骰子仍在地上的n
:param current: 剩餘需要遞歸的骰子數
:param sum: 骰子點數相加的數
:param pProbabilities:
:return:
'''
# 遞歸結束條件
if current == 1:
pProbabilities[sum - original] += 1
else:
for i in range(1, self.g_maxValue + 1):
self.getProbability(original, current - 1, i + sum, pProbabilities)
(2)動態規劃解法
使用動態規劃解決問題一般分爲三步:
- 表示狀態
- 找出狀態轉移方程
- 邊界處理
下面我們一步一步分析,相信你一定會有所收穫!
1. 表示狀態
分析問題的狀態時,不要分析整體,只分析最後一個階段即可!因爲動態規劃問題都是劃分爲多個階段的,各個階段的狀態表示都是一樣,而我們的最終答案就是在最後一個階段。
對於這道題,最後一個階段是什麼呢?
通過題目我們知道一共投擲 n 枚骰子,那最後一個階段很顯然就是:當投擲完 n 枚骰子後,各個點數出現的次數。
注意,這裏的點數指的是前 n 枚骰子的點數和,而不是第 n 枚骰子的點數,下文同理。
找出了最後一個階段,那狀態表示就簡單了。
- 首先用數組的第一維來表示階段,也就是投擲完了幾枚骰子。
- 然後用第二維來表示投擲完這些骰子後,可能出現的點數。
- 數組的值就表示,該階段各個點數出現的次數。
所以狀態表示就是這樣的:,表示投擲完 i 枚骰子後,點數 j 的出現次數。
2. 找出狀態轉移方程
找狀態轉移方程也就是找各個階段之間的轉化關係,同樣我們還是隻需分析最後一個階段,分析它的狀態是如何得到的。
最後一個階段也就是投擲完 n 枚骰子後的這個階段,我們用 來表示最後一個階段點數 j 出現的次數。
單單看第 n 枚骰子,它的點數可能爲 ,因此投擲完 n 枚骰子後點數 j 出現的次數,可以由投擲完 枚骰子後,對應點數 出現的次數之和轉化過來。
for (第n枚骰子的點數 i = 1; i <= 6; i ++) {
dp[n][j] += dp[n-1][j - i]
}
寫成數學公式是這樣的:
n 表示階段,j 表示投擲完 n 枚骰子後的點數和,i 表示第 n 枚骰子會出現的六個點數。
3. 邊界處理
這裏的邊界處理很簡單,只要我們把可以直接知道的狀態初始化就好了。
我們可以直接知道的狀態是啥,就是第一階段的狀態:投擲完 1 枚骰子後,它的可能點數分別爲 ,並且每個點數出現的次數都是 1 。
for (int i = 1; i <= 6; i ++) {
dp[1][i] = 1;
}
代碼:
# 動態規劃解法
def twoSum2(self, n: int) -> List[float]:
# 初始化二維數組
dp = [[0 for _ in range(6 * n + 1)] for _ in range(n + 1)]
# 初始化第一行
for i in range(1, 7):
dp[1][i] = 1
for i in range(2, n + 1):
for j in range(i, i * 6 + 1):
for k in range(1, 7):
if j >= k + 1:
dp[i][j] += dp[i - 1][j - k]
res = []
for i in range(n, n * 6 + 1):
res.append(dp[n][i] * 1.0 / 6 ** n)
return list(map(lambda x: round(x, 5), res))
Reference
【1】《劍指offer》,何海濤著。
【2】Leetcode n個骰子的點數---動態規劃,地址:https://blog.csdn.net/qq_24243877/article/details/104560944
【3】【n個骰子的點數】:詳解動態規劃及其優化方式,地址:https://leetcode-cn.com/problems/nge-tou-zi-de-dian-shu-lcof/solution/nge-tou-zi-de-dian-shu-dong-tai-gui-hua-ji-qi-yo-3/