zoukankan      html  css  js  c++  java
  • 剑指Offer对答如流系列

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

    题目描述

    把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。

    问题分析

    这个问题需要点高中数学的知识。

    对于n个骰子,要计算出每种点数和的概率,我们知道投掷n个骰子的总情况一共有6^n种,因此只需要计算出某点数和的情况一共有几种,即可求出该点数之和的概率。

    思路一:递归暴力

    我们知道点数之和s的最小值为n,最大值为6*n,因此考虑用一个大小为(6*n-n+1)的数组存放不同点数之和的情况个数,那么,如果点数之和为x,那么把它出现的情况总次数放入数组种下标为x-n的元素里。

    确定了如何存放不同点数之和的次数之后,我们要计算出这些次数。我们把n个骰子分为1个骰子和n-1个骰子,这1个骰子可能出现1~6个点数,由该骰子的点数与后面n-1个骰子的点数可以计算出总点数;而后面的n-1个骰子又可以分为1个和n-2个,把上次的点数,与现在这个骰子的点数相加,再和剩下的n-2个骰子的点数相加可以得到总点数……,即可以用递归实现。在获得最后一个骰子的点数后可以计算出几个骰子的总点数,令数组中该总点数的情况次数+1,即可结束遍历。

    可以感受到,计算量实在太大了,效率会比较低。

    思路二:动态规划

    剑指Offer的解答过于繁琐与复杂。这里提供一种相对理解简单,容易实现的方法。

    我们通过分析能够发现 n个骰子的点数依赖于n-1个骰子的点数,相当于在n-1个骰子点数的基础上再进行投掷。

    由此定义状态转移方程为f(n,k)表示n个骰子点数和为k时出现的次数,于是可得:

    f(n,k)=f(n−1,k−1)+f(n−1,k−2)+f(n−1,k−3)+f(n−1,k−4)+f(n−1,k−5)+f(n−1,k−6)

    其中 n>0且k<=6n,f(n−1,k−i)表示的是第n次掷骰子时,骰子的点数为i对应的情况

    从k−1到k−6分别对应第n次掷骰子时骰子正面为1到6的情况。而初始状态可以定义为:

    f(1,1)=f(1,2)=f(1,3)=f(1,4)=f(1,5)=f(1,6)=1

    问题解答

    思路一:

        private final int maxValue = 6;
    
        public  void printProbability1(int number) {
            if(number<=0) {
                return;
            }
    
            int[] probabilities = new int[maxValue*number-number+1];
            Arrays.fill(probabilities,0);
    
            //计算不同点数出现的次数
            for(int i=1;i<=maxValue;i++) {
                //第一次掷骰子,总点数只能是1~maxValue(即6)
                calP(probabilities, number, number-1, i);
            }
            
            //所有情况总共出现的次数
            int totalP = (int) Math.pow(maxValue, number);  
            for(int i=0; i<probabilities.length; i++) {
                double ratio = (double)probabilities[i]/totalP;
                NumberFormat format = NumberFormat.getPercentInstance();
                format.setMaximumFractionDigits(2);//设置保留几位小数
                System.out.println("点数和为"+(i+number)+"的概率为:"+format.format(ratio));
            }
        }
    
        private  void calP(int[] probabilities, int number, int curNumber, int sum) {
            if(curNumber==0) {
                probabilities[sum-number]++; //总数为sum的情况存放在sum-number下标中
                return;
            }
            for(int i=1; i<=maxValue; i++) {
                // 相当于剩余的骰子少一个,总点数增加。
                calP(probabilities, number, curNumber-1, sum+i);  
            }
        }
    

    思路二:

      public  void printProbability(int number) {
            double total = Math.pow(6,number);
            for(int i=number; i<=6*number; i++) {
                double ratio = (double)getNSumCount(number,i)/total;
                NumberFormat format = NumberFormat.getPercentInstance();
                format.setMaximumFractionDigits(2);//设置保留几位小数
                System.out.println("点数和为"+getNSumCount(number,i)+"的概率为:"+format.format(ratio));
            }
        }
    
        private int getNSumCount(int n, int sum) {
            if(n<1 || sum<n || sum>6*n) {
                return 0;
            }
            if(n==1) {
                return 1;
            }
            int resCount;
            resCount = getNSumCount(n-1,sum-1)+getNSumCount(n-1,sum-2)+
                    getNSumCount(n-1,sum-3)+getNSumCount(n-1,sum-4)+
                    getNSumCount(n-1,sum-5)+getNSumCount(n-1,sum-6);
            return resCount;
        }
    
  • 相关阅读:
    MVC根据角色自动选择母版页
    Redis学习笔记~五大数据结果的测试
    Redis学习笔记~Redis提供的五种数据结构
    将不确定变为确定~一切归总为“二”(C#中的位运算有啥用)
    Redis学习笔记~把redis放在DATA层,作为一种数据源,我认为更合理,也更符合我的面向对象原则
    屌丝程序员的那些事(一)毕业那年
    jquery的Flexigrid改造,支持选中行内容获取,两个表格行相互移动,行选中事件,支持dwr
    屌丝程序员的那些事(三)一起培训的那些人
    Centos 64位下搭建android开发环境需要的lib包
    屌丝程序员的那些事(二)第一次面试
  • 原文地址:https://www.cnblogs.com/JefferyChenXiao/p/12249457.html
Copyright © 2011-2022 走看看