【Leetcode】《剑指offer-面试题43》n个骰子的点数

我的梦境 提交于 2020-08-05 04:24:24

我的个人微信公众号: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/

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!