zoukankan      html  css  js  c++  java
  • 剑指Offer解题报告(Java版)——n个骰子的点数 43

    问题

       

    n个骰子朝上的数之和为s,求s的所有可能以及概率

       

    分析问题

       

    如果是用笨方法,一般人最开始都会想到笨方法,那就是枚举法

       

    举个例子,比如两个骰子,第一个骰子的结果为1,2,3,4,5,6,两个骰子的结果是2,3,4,5,6,7;3,4,5,6,7,8;4,5,6,7,8,9;……7,8,9,10,11,12,共三十六种,用n平方size的数组记录这36个结果

       

    仔细分析可以发现其实这其中有很多是重复的,所以去除重复,考虑最小的应该是2,也就是n,最大的应该是12,也就是6n

       

    所以所有的结果应该只有6n-n+1=5n+1种,如果我们开辟一个最大index=6n的数组也就是size为6n+1的数组就可以放下这所有的结果,但是其中index为0-(n-1)的位置上没有数放,这里我们有两种解决方案,一种是就让它空着,这样的好处是,结果为s的就可以直接放在index为s的位上,不过如果我们想节省这部分的空间,可以将所有数据往前移一下,也就是把和为s的放在s-n上即可,这样我们就只需要size为5n+1的数组

       

    所以我们再声明一个结果数组,5n+1大小,通过遍历前面的n平方大小的数组,出现和为s就在5n+1大小的s-n位上加1即可

       

    这样的方式,时间复杂度为n平方,可见并不理想,我们可以降低时间复杂度

       

    首先想到是否能退化问题,比如n个骰子与n-1个骰子之间的关系,比如n个骰子的结果是n-1个骰子的结果分别加上1-6而得,于是n-1个骰子的结果又是n-2个骰子的结果分别再加上1-6所得

       

    但递归的方法并不是很好,很多重复计算,重复计算的问题可以考虑斐波拉契计算过程,我们最后提出一种以空间换时间的方法,也是传统的记录中间结果的方法,根斐波拉契的优化很像,将某些中间结果存起来以减少递归过程的重复计算问题

       

    解决问题

       

    主体在如何计算次数,将次数存到数组中,由于要用到递归,我们最好单独写一个base函数,这是我的经验,base函数中的参数要包括递归的时候要用到的那些变量,比如总的n,现在的n,以及现在的sum,以及贯穿始终的次数数组

       

    static void baseProbabilities(int numTotal,int numCur,int sum,int[] probabilities){

    if (numCur==1) {

    probabilities[sum-numTotal]++;

    }else {

    for (int i = 1; i <=g_maxValue ; i++) {

    baseProbabilities(numTotal, numCur-1, sum+i, probabilities);

    }

    }

    }

    而计算次数的时候就是去调用这个base的函数

       

    for (int i = 1; i <=g_maxValue; i++) {

    baseProbabilities(number, number, i, probabilities);

    }

       

    考虑n=2时的递归过程,首先nT=2,nC=2,sum=1,表明第一个骰子甩出一个1,由于nC=2表明现在有两个骰子,所以进入else部分,i又从1到6循环,表明这是进入到第二个骰子在甩了,首先i为1,表明又甩出一个1,这时候nC=1,就将2-n的位置上加1,表明结果为2的次数加1,然后退到上一层,i++,此时还是第二个骰子在甩,甩出一个2,此时sum=3,nC=1,所以在和为3的位置上加1,一直这样,到了和为7的位置上加1的时候,会退到在上一次循环,这时候表明第一个骰子甩出了一个2,此时进入第二个骰子,依次会出现和为3,4,5,6,7,8的结果,然后再在相应位置上加1即可

       

    优化方法

       

    我们需要将中间值存起来以减少递归过程中的重复计算问题,可以考虑我们用两个数组ABAB之上得到,B又在A之上再次得到,这样AB互相作为对方的中间值,其实这个思想跟斐波拉契迭代算法中用中间变量保存n-1,n-2的值有异曲同工之妙

       

    我们用一个flag来实现数组AB的轮换,由于要轮转,我们最好声明一个二维数组,这样的话,如果flag=0时,1-flag用的就是数组1,如果flag=1时,1-flag用的就是数组0,

       

    int[][] probabilities=new int[2][];

    probabilities[0]=new int[g_maxValue*number+1];

    probabilities[1]=new int[g_maxValue*number+1];

       

    我们以probabilities[0]作为初始的数组,那么我们对这个数组进行初始化是要将1-6都赋值为1,说明第一个骰子投完的结果存到了probabilities[0]

       

    然后就是第二个骰子,第二个骰子的结果存到probabilities[1],是以probabilities[0]为基础的,此时和为s的次数就是把probabilities[0]中和为s-1,s-2,s-3,s-4,s-5,s-6的次数加起来即可

       

    int temp=0;

    for(int j=1;j<=i && j<=g_maxValue;++j)

    temp+=probabilities[flag][i-j];

    probabilities[1-flag][i]=temp;

       

    而第k次用k个骰子那么要更新的结果范围就是k到maxValue*k

       

    所以连起来就是

       

    for(int i=k;i<=g_maxValue*k;++i)

    {

    int temp=0;

    for(int j=1;j<=i && j<=g_maxValue;++j)

    temp+=probabilities[flag][i-j];

    probabilities[1-flag][i]=temp;

    }

       

    然后就需要把probabilities[1]作为中间值数组,这里我们把flag赋值为1-flag即可,是不是很神奇!

       

    flag=1-flag;

  • 相关阅读:
    Payload Header到底是什么
    usb bulk传输和同步传输
    Video streaming interface 带宽的选择
    Methyl-SeqDNA的甲基化图谱|DNase I-Seq|ChIP-Seq|3C-Seq|
    连词词组|relax|brings about a rise in|Chance are (high)that|Have no clue|Be passionate about|Tedious|overwhelmed by piles of
    body书写总框架
    单个body|简单解释|复杂解释|反面解释
    TS写法
    saturates|meteoric|enclose|marooned|predators|Pioneer community|salinization|condenser|embodied
    否定事实信息题
  • 原文地址:https://www.cnblogs.com/keedor/p/4474471.html
Copyright © 2011-2022 走看看