面试题43: n个骰子的点数

原题:把n个骰子扔到地上,所有骰子朝上一面的点数之后为s. 输入n,打印出s所有可能的值出现的概率。(每个骰子6个面,点数从1到6)

解法一:基于递归,时间效率不高

递归的思想一般是分而治之,把n个骰子分为第一个和剩下的n-1个。先计算第一个骰子每个点数出现的次数,再计算剩余n-1个骰子出现的点数之和。求n-1个骰子的点数之的方法和前面讲的一样,即再次把n-1个骰子分成两堆------第一个和剩下的n-2个。n个骰子,每个骰子6个面,总共有6n个组合。这6n个组合之中肯定有重复的,我们知道其范围是n~6n,对于每种情况我们可以用缓存机制记录下来,每当其发生一次我们令其对应的单元加1。

我们定义一个长度为6n-n+1的数组,和为s的点数出现的次数保存到数组第s-n个元素里。为什么是6n-n+1呢?因为n个骰子的和最少是n,最大是6n,介于这两者之间的每一个情况都可能会发生,总共6n-n+1种情况。

      private static final int g_maxValue = 6;
      //基于递归求骰子点数,时间效率不高
      public static void PrintProbability(int number){
          if(number<1) return;
          int maxSum = number*g_maxValue;
          int[] pProbabilities = new int[maxSum-number+1];
          //初始化,开始统计之前都为0次
          for(int i=number;i<=maxSum;i++){
              pProbabilities[i-number] = 0;
         }
         double total = Math.pow(g_maxValue,number);
         //probability(number,pProbabilities);这个函数计算n~6n每种情况出现的次数
         probability(number,pProbabilities);
         for(int i=number;i<=maxSum;i++){
             double ratio = pProbabilities[i-number]/total;
             System.out.println("i: "+i+" ratio: "+ratio);
        }
     }
     public static void probability(int number,int[] pProbabilities){
        for(int i=1;i<=g_maxValue;i++){//从第一个骰子开始
             probability(number,number,i,pProbabilities);
         }
    }
     //总共original个骰子,当前第 current个骰子,当前的和,贯穿始终的数组
     public static void probability(int original,int current,int sum,int[] pProbabilities){
       if(current==1){
             pProbabilities[sum-original]++;
         }else{
             for(int i=1;i<=g_maxValue;i++){
                probability(original,current-1,sum+i,pProbabilities);
             }
        }
     }
考虑 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即可

解法二:基于循环,时间性能好

递归一般是自顶向下的分析求解,而基于循环的方法则是自底向上。基于循环的一般需要更少的空间和更少的时间,性能较好,但是一般代码比较难懂。

      //基于循环求骰子点数
      public static void PrintProbability_1(int number){
          if(number<1){
              return;
          }
          int[][] pProbabilities = new int[2][g_maxValue*number +1];
          for(int i=0;i<g_maxValue;i++){//初始化数组
               pProbabilities[0][i] = 0;
               pProbabilities[1][i] = 0;
         }
         int flag = 0;
         for(int i=1;i<=g_maxValue;i++){//当第一次抛掷骰子时,有6种可能,每种可能出现一次
             pProbabilities[flag][i] = 1;//我们以probabilities[0]作为初始的数组,那么我们对这个数组进行初始化是要将1-6都赋值为1,说明第一个骰子投完的结果存到了probabilities[0]
         }
         //从第二次开始掷骰子,假设第一个数组中的第n个数字表示骰子和为n出现的次数,
         //在下一循环中,我们加上一个新骰子,此时和为n的骰子出现次数应该等于上一次循环中骰子点数和为n-1,n-2,n-3,n-4,n-5,
         //n-6的次数总和,所以我们把另一个数组的第n个数字设为前一个数组对应的n-1,n-2,n-3,n-4,n-5,n-6之和
         for(int k =2;k<=number;k++){
             for(int i=0;i<k;i++){//第k次掷骰子,和最小为k,小于k的情况是不可能发生的!所以另不可能发生的次数设置为0!
                 pProbabilities[1-flag][i] = 0;
             }
             for(int i=k;i<=g_maxValue*k;i++){//第k次掷骰子,和最小为k,最大为g_maxValue*k
                 pProbabilities[1-flag][i] = 0;//初始化,因为这个数组要重复使用,上一次的值要清0
                 for(int j=1;j<=i&&j<=g_maxValue;j++){
                     pProbabilities[1-flag][i] += pProbabilities[flag][i-j];
                 }
             }
             flag = 1-flag;
         }
         double total = Math.pow(g_maxValue, number);
         for(int i=number;i<=g_maxValue*number;i++){
             double ratio = pProbabilities[flag][i]/total;
             System.out.println("sum: "+i+" ratio: "+ratio);
         }
     }





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值