leetcode:剑指 Offer 60. n个骰子的点数

题目来源

题目描述

在这里插入图片描述

class Solution {
public:
    vector<double> dicesProbability(int n) {

    }
};

题目解析

题目要求我们求出所有点数(即点数和)出现的概率,根据概率的计算公式,点数k出现的概率就是

P ( K ) = k 出 现 的 次 数 / 总 次 数 P_{(K)} = k出现的次数/总次数 P(K)=k/

又,给定n个骰子:

  • 每枚骰子的点数都有 6 种可能出现的情况,每个骰子摇到1至6的概率相等,都为 1 / 6 1/6 1/6
  • 将每个骰子的点数看作独立情况,共有 6 n 6^n 6n种[点数组合]。例如 n = 2n=2 时的点数组合为:
    ( 1 , 1 ) , ( 1 , 2 ) , ⋯ , ( 2 , 1 ) , ( 2 , 2 ) , ⋯ , ( 6 , 1 ) , ⋯ , ( 6 , 6 ) (1,1),(1,2),⋯,(2,1),(2,2),⋯,(6,1),⋯,(6,6) (1,1),(1,2),,(2,1),(2,2),,(6,1),,(6,6)
  • n 个骰子「点数和」的范围为 [ n , 6 n ] [n, 6n] [n,6n],数量为 6 n − n + 1 = 5 n + 1 6n - n + 1 = 5n + 1 6nn+1=5n+1种。

即,回答上面问题:

  • 所有点数出现的总次数是 6 n 6^n 6n

所以下面的目标是计算出投掷完 n枚骰子后每个点数出现的次数

暴力

暴力统计: 每个「点数组合」都对应一个「点数和」,考虑遍历所有点数组合,统计每个点数和的出现次数,最后除以点数组合的总数(即除以 6 n 6^n 6n),即可得到每个点数和的出现概率。

如下图所示,为输入 n = 2时,点数组合、点数和、各点数概率的计算过程。

在这里插入图片描述

暴力的本质是递归。我们需要计算出n枚骰子每个点数出现的次数。可以使用递归函数 g e t C o u n t ( n , k ) getCount(n, k) getCount(n,k)来表示投掷 n枚骰子,点数 k 出现的次数。

为简化分析,我们以2枚骰子为例。

我们来模拟计算点数4和点数6,这两种点数各自出现的次数。也就是计算getCount(2, 4)和getCount(2, 6)

它们的计算公式为:
g e t C o u n t ( 2 , 4 ) = g e t C o u n t ( 1 , 1 ) + g e t C o u n t ( 1 , 2 ) + g e t C o u n t ( 1 , 3 ) getCount(2,4)=getCount(1,1)+getCount(1,2)+getCount(1,3) getCount(2,4)=getCount(1,1)+getCount(1,2)+getCount(1,3)
g e t C o u n t ( 2 , 6 ) = g e t C o u n t ( 1 , 1 ) + g e t C o u n t ( 1 , 2 ) + g e t C o u n t ( 1 , 3 ) + g e t C o u n t ( 1 , 4 ) + g e t C o u n t ( 1 , 5 ) getCount(2,6)=getCount(1,1)+getCount(1,2)+getCount(1,3)+getCount(1,4)+getCount(1,5) getCount(2,6)=getCount(1,1)+getCount(1,2)+getCount(1,3)+getCount(1,4)+getCount(1,5)

我们发现递归统计这两种点数的出现次数时,重复计算了

g e t C o u n t ( 1 , 1 ) , g e t C o u n t ( 1 , 2 ) , g e t C o u n t ( 1 , 3 ) getCount(1, 1) , getCount(1, 2) , getCount(1, 3) getCount(1,1),getCount(1,2),getCount(1,3)

这些子结构,计算其它点数的次数时同样存在大量的重复计算。

动态规划

(1)定义状态

  • 分析问题的状态时,不要分析整体,只分析最后一个阶段即可!因为动态规划问题都是划分为多个阶段的,各个阶段的状态表示都是一样,而我们的最终答案在就是在最后一个阶段。
  • 最后一步:
    • 最后一个阶段是:当投掷完 n 枚骰子后,各个点数出现的次数。
    • 注意,这里的点数指的是前 n 枚骰子的点数和,而不是第 n 枚骰子的点数,下文同理。
  • 分析状态:
    • 首先用数组的第一维表示阶段,也就是投掷完了几枚骰子。
    • 然后用第二维来表示投掷完这些骰子后,可能出现的点数。
    • 数组的值就表示,该阶段各个点数出现的次数。
  • 定义状态: d p [ i ] [ j ] dp[i][j] dp[i][j]表示投掷完 i 枚骰子后,点数j的出现次数

(2)状态转移方程

  • 找状态转移方程也就是找各个阶段之间的转化关系,同样我们还是只需分析最后一个阶段,分析它的状态是如何得到的。
  • 最后一个阶段也就是投掷完 n 枚骰子后的这个阶段,我们用 dp[n][j]来表示最后一个阶段点数 j 出现的次数。
  • 单单看第n枚骰子,它的点数可能为 1 、 2 、 . . . . . 6 1、2、.....6 12.....6,因此投掷完n没骰子后点数j出现的次数,可以由投掷完 n-1枚骰子后,对应点数 j − 1 , j − 2 , j − 3 , . . . , j − 6 j-1, j-2, j-3, ... , j-6 j1,j2,j3,...,j6 出现的次数之和转化过来。
for (第n枚骰子的点数 i = 1; i <= 6; i ++) {
    dp[n][j] += dp[n-1][j - i]
}

(3)边界处理

  • 这里的边界处理很简单,只要我们把可以直接知道的状态初始化就好了。
  • 我们可以直接知道的状态是啥,就是第一阶段的状态:投掷完 1 枚骰子后,它的可能点数分别为 1 , 2 , 3 , . . . , 6 1, 2, 3, ... , 6 1,2,3,...,6并且每个点数出现的次数都是 1
 for (int i = 1; i <= 6; i ++) {
            dp[1][i] = 1;
}

(4)计算顺序

class Solution {
public:
    vector<double> dicesProbability(int n) {
        int dp[15][70];
        memset(dp, 0, sizeof(dp));

        for (int i = 1; i <= 6; ++i) {
            dp[1][i] = 1;
        }

        for (int i = 2; i <= n; ++i) { // 前n枚骰子
            for (int j = i; j < 6 * i; ++j) {  //点数和
                for (int curr = 1; curr <= 6; ++curr) {  //j可能由j - 1, j - 2,  ... j - 6投转换而来
                    if(j - curr <= 0){
                        break;
                    }
                    dp[i][j] += dp[i - 1][j - curr];
                }
            }
        }
        
        int all = pow(6, n);
        std::vector<double > ret;
        for (int i = n; i <= 6 * n; ++i) {
            ret.emplace_back(dp[n][i] * 1.0 / all);
        }
        return ret;
    }
};

(4)空间优化

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
org.elasticsearch.client.ResponseException: method [PUT], host [http://192.168.93.132:9200], URI [/TestIndex], status line [HTTP/1.1 406 Not Acceptable] {"error":"Content-Type header [application/vnd.elasticsearch+json; compatible-with=8] is not supported","status":406} at org.elasticsearch.client.RestClient.convertResponse(RestClient.java:347) at org.elasticsearch.client.RestClient.performRequest(RestClient.java:313) at org.elasticsearch.client.RestClient.performRequest(RestClient.java:288) at co.elastic.clients.transport.rest_client.RestClientTransport.performRequest(RestClientTransport.java:146) at co.elastic.clients.elasticsearch.indices.ElasticsearchIndicesClient.create(ElasticsearchIndicesClient.java:266) at co.elastic.clients.elasticsearch.indices.ElasticsearchIndicesClient.create(ElasticsearchIndicesClient.java:282) at com.sora.leetcode_notebook.ElasticSearch.ElasticsearchJavaApi.createIndex(ElasticsearchJavaApi.java:52) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26) at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27) at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63) at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329) at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293) at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) at org.junit.runners.ParentRunner.run(ParentRunner.java:413) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69) at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38) at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11) at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35) at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235) at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
最新发布
06-06

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值