【Golang】LeetCode-剑指Offer-面试题60-n个骰子的点数

题目

把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。
输入n,打印出s的所有可能的值出现的概率。
你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率。

示例 1:
输入: 1
输出: [0.16667,0.16667,0.16667,0.16667,0.16667,0.16667]

示例 2:
输入: 2
输出:
[0.02778,0.05556,0.08333,0.11111,0.13889,0.16667,0.13889,0.11111,0.08333,0.05556,0.02778]

限制:

1 <= n <= 11

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/nge-tou-zi-de-dian-shu-lcof


解题思路

解题思路引用自 LeetCode 作者 huwt :点击这里访问题解原文

  • 析题

    • 一个骰子有6个面,6种结果 ( n=1 )
    • n 个骰子有 6n-1 种点数和结果,点数和的大小范围为 n~6n-1 ( n>=2 )
    • 题目的结果其实就是计算所有骰子点数和出现的次数,然后除以总的可能数
    • 总的可能性容易求出,为 6 ^ n(6的n次方)
    • 问题的关键在于求解每一个点数和出现的次数
  • 动态规划解题

    • 使用动态规划解决问题一般分为三步:
      1. 表示状态
      2. 找出状态转移方程
      3. 边界处理

逐步分析

1. 表示状态

  • 分析问题的状态时,不要分析整体,只分析最后一个阶段即可!

    • 因为动态规划问题都是划分为多个阶段的,各个阶段的状态表示都是一样,而我们的最终答案就是在最后一个阶段。
  • 最后一个阶段是什么呢?

    • 通过题目我们知道一共投掷 n 枚骰子,那最后一个阶段很显然就是:
    • 当投掷完 n 枚骰子后,各个点数出现的次数(前 n 枚骰子的点数和)。

注意,这里的点数指的是前 n 枚骰子的点数和,而不是第 n 枚骰子的点数,下文同理。

  • 找出了最后一个阶段,如何表示状态

    • 首先用 变量 t = n 来表示需要掷多少枚骰子,每掷完一枚骰子 t - 1 。
    • 定义一个 map
    • 用 map 的 key 来表示投掷完这些骰子后,可能出现的点数和。
    • map 的 value 就表示,该阶段各个点数出现的次数。

所以状态表示就是这样的:map [int] int ,表示投掷完 n 枚骰子后,点数 key 的出现次数 value 。


2. 找出状态转移方程

  • 也就是找各个阶段之间的转化关系,同样只需分析最后一个阶段。

    • 最后一个阶段 t = 0 也就是投掷完 n 枚骰子后的这个阶段
  • 单单看一枚骰子,它的点数可能为 1, 2, 3, … 6

    • 因此投掷完 n 枚骰子后点数和出现的次数,可以由投掷完 n - 1 枚骰子后,对应点数+第 n 枚骰子会出现的六个点数(1~6)的点数和,出现的次数之和
 	t := n
    m := map[int]int{0:1}
    for t > 0 {
        m1 := map[int]int{}
        for i := 1; i <= 6; i++ {
            for k, v := range m {
                m1[k + i] += v
            }
        }
        m = m1
        t--
    }

t 表示阶段,k + i 表示投掷完 n 枚骰子后的点数和,i 表示第 n 枚骰子会出现的六个点数。


3. 边界处理

  • 这里的边界处理很简单,只要我们把可以直接知道的状态初始化就好了。

  • 我们可以直接知道的状态是啥,就是第一阶段的状态:

  • 投掷完 1 枚骰子后,它的可能点数分别为 1, 2, 3, … , 6 并且每个点数出现的次数都是 1

 	m1 := map[int]int{}
 	for i := 1; i <= 6; i++ {
 	    for k, v := range m {
 	        m1[k + i] += v
 	    }
 	}
 	m = m1

代码

–执行用时:0 ms --内存消耗:2.1 MB

func twoSum(n int) []float64 {
 	//表示阶段
    t := n
    //每个点数出现的次数都是 1
    m := map[int]int{0:1}
    for t > 0 {
     	//掷第 n 枚骰子时初始化
        m1 := map[int]int{}
        //边界处理
        for i := 1; i <= 6; i++ {
            for k, v := range m {
             	//k + i 表示投掷完 n 枚骰子后的点数和
             	// += v n-1 个骰子时对应的点数+第 n 枚骰子会出现的六个点数(1~6)的点数和的出现次数
                m1[k + i] += v
            }
        }
        // 保存 n-1 个骰子时对应点数的出现次数
        m = m1
        t--
    }
    //掷骰总次数
    sum := 0
    for _, v := range m {
        sum += v
    }
    //sum := math.Pow(6.0,float64(n))
    //计算每个点数和的概率
    ret := make([]float64, n * 6 - n + 1)
    for k, v := range m {
     	//数组下标刚好为 k - n
        ret[k - n] = float64(v)/float64(sum)
    }
    return ret
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值