剑指 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

题解

题解1: 动态规划

动态规划三步曲:

  1. 状态表示
    dp[n][s]表示n个骰子,n 个朝上的面的点数之和为 s 的事件出现的次数。
  2. 转移方程
    dp[n][s] += dp[n - 1][s - k],k 属于 [1, 6]。当前状态只能由前一状态转移而来.
  3. 初始化
    投一个骰子只能有6种不同情况,其他状态都是由一个骰子的状态转移而来
for (int s = 1; s <= 6; ++s) {
    // 初始化 dp 数组
    dp[1][s] = 1;
}

整体代码如下:

class Solution {
public:
    vector<double> dicesProbability(int n) {
        vector<double> res;
        vector<vector<int> > dp(n + 1, vector<int>(6 * n + 1, 0)); //将全部值初始化为 0
        // dp[n][s]表示n个骰子,n 个朝上的面的点数之和为 s 的事件出现的次数。
        int row = dp.size(), col = dp[0].size();
        for (int s = 1; s <= 6; ++s) {
            // 初始化 dp 数组
            dp[1][s] = 1;
        }
        for (int n = 2; n < row; ++n) {
            for (int s = n; s < col; ++s) {
                for (int k = 1; k <= 6; ++k) {
                    if (s - k >= n-1) {//n-1个骰子点数和最小为n-1
                        dp[n][s] += dp[n - 1][s - k];
                    } else {
                        break;
                    }
                }
            }
        }
        double denominator = pow(6.0, n); // 分母
        for (int s = n; s <= 6 * n; ++s) {
            res.push_back(dp[n][s] / denominator);
        }
        return res;
    }
};

题解2: 动态规划(状态压缩)

由于每个阶段(骰子数n)的状态都只和它前一阶段(点数s-1)的状态有关,因此我们不需要用额外的一维来保存所有阶段。

用一维数组来保存一个阶段的状态,然后对下一个阶段可能出现的点数 s 从大到小遍历,实现一个阶段到下一阶段的转换。

class Solution {
public:
    vector<double> dicesProbability(int n) {
        vector<double> res;
        vector<int> dp(6 * n + 1, 0); //将全部值初始化为 0
        // dp[n][s]表示n个骰子,n 个朝上的面的点数之和为 s 的事件出现的次数。
        for (int s = 1; s <= 6; s++) {
            // 初始化 dp 数组
            dp[s] = 1;
        }
        for (int i = 2; i <= n; i++) {
            for (int s = 6*n; s >= i; s--) {//点数最小值为i个骰子均为1之和
                dp[s] = 0;//因为是从后往前逐个累加,在加到当前点数时,必须把原先存放的n-1个骰子的数据置0,否则累加出错
                for (int k = 1; k <= 6; k++) {
                    if (s - k >= i-1) {//n-1个骰子点数和至少为n-1,当前循环使用i个骰子,所以s-k>=i-1
                        dp[s] += dp[s - k];
                        //cout << "dp[" << s << "]=" << dp[s] << endl;
                    } else {
                        break;
                    }
                }
            }
        }
        double denominator = pow(6.0, n); // 分母
        for (int s = n; s <= 6 * n; s++) {
            res.push_back(dp[s] / denominator);
        }
        return res;
    }
};

参考

字节题库 - #剑60 - 中等 - n个骰子的点数 - 1刷
【n个骰子的点数】:详解动态规划及其优化方式
剑指 Offer 60. n 个骰子的点数(动态规划,清晰图解)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值