【Leetcode】《劍指offer-面試題43》n個骰子的點數

我的個人微信公衆號:Microstrong

微信公衆號ID:MicrostrongAI

微信公衆號介紹:Microstrong(小強)同學主要研究機器學習、深度學習、計算機視覺、智能對話系統相關內容,分享在學習過程中的讀書筆記!期待您的關注,歡迎一起學習交流進步!

知乎主頁:https://www.zhihu.com/people/MicrostrongAI/activities

Github:https://github.com/Microstrong0305

個人博客:https://blog.csdn.net/program_developer

題目鏈接

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 枚骰子的點數,下文同理。

找出了最後一個階段,那狀態表示就簡單了。

  • 首先用數組的第一維來表示階段,也就是投擲完了幾枚骰子。
  • 然後用第二維來表示投擲完這些骰子後,可能出現的點數。
  • 數組的值就表示,該階段各個點數出現的次數。

所以狀態表示就是這樣的:dp[i][j],表示投擲完 i 枚骰子後,點數 j 的出現次數。

2. 找出狀態轉移方程

找狀態轉移方程也就是找各個階段之間的轉化關係,同樣我們還是隻需分析最後一個階段,分析它的狀態是如何得到的。

最後一個階段也就是投擲完 n 枚骰子後的這個階段,我們用 dp[n][j] 來表示最後一個階段點數 j 出現的次數。

單單看第 n 枚骰子,它的點數可能爲 1 , 2, 3, ... , 6,因此投擲完 n 枚骰子後點數 j 出現的次數,可以由投擲完 n-1 枚骰子後,對應點數 j-1, j-2, j-3, ... , j-6 出現的次數之和轉化過來。

for (第n枚骰子的點數 i = 1; i <= 6; i ++) {
    dp[n][j] += dp[n-1][j - i]
}

寫成數學公式是這樣的:

dp[n][j] = \sum_{i=1}^6 dp[n-1][j-i]

n 表示階段,j 表示投擲完 n 枚骰子後的點數和,i 表示第 n 枚骰子會出現的六個點數。

3. 邊界處理

這裏的邊界處理很簡單,只要我們把可以直接知道的狀態初始化就好了。

我們可以直接知道的狀態是啥,就是第一階段的狀態:投擲完 1 枚骰子後,它的可能點數分別爲 1, 2, 3, ... , 6,並且每個點數出現的次數都是 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/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章