您的位置:首页 > 职场人生

概率相关高频面试题 如何求最大公约数

2017-07-21 18:00 344 查看
概率的应用
http://blog.csdn.net/djangobuaa/article/details/51133147这一节可以参考这篇博客,里面对每道题都有详细的解析!!!
1. 利用随机来改进著名算法(快速排序)

2. 随机数发生器(用给定的随机数发生器构造另一个)

案例一:8只球队,有三个强队,其余都是弱队,随机把他们分成4组比赛,每组两个队,问两强相遇的概率是多大?

利用排列组合,分别计算出分子和分母。

1. 首先求出8只球队分成4组比赛的方法数。

7*5*3*1=105

2. 没有两强相遇的方法数。

也就是在5只弱队中选出3只与强队配对,剩下的2只自行配对。

从5只弱队中选出3只弱队的方法数为:C(5,3),3只强队和3只弱队彼此配对的方法数为:A(3.3)

所以结果为:C(5,3)*A(3,3)=60

3. 求两强不相遇的方法数为:

(105-60)/105=3/7;

最大公约数(Greatest Common Divisor,GCD)的算法有两种:

1. 辗转相除法,也叫欧几里得算法。

假设m>n,计算m除以n,将得到的余数记为r。如果r=0,那么n就是求得的最大公约数,否则继续向下执行。

向下执行的过程中,将m和n中较小的数也就是除数n作为被除数,将r作为除数,也就是m=n,n=r,然后重复上面的步骤。知道

余数为0,此时的n就是最大公约数。

2. 更相减损法

3. Stein算法

辗转相除法的缺点:计算机中的整数最多是64位,如果参与运算的整数低于64位,那么取模的算法比较简单,直接用%就可以。对于计算两个超过64位的整数的模,就不能用辗转相除法。因此,在计算超过64位的整数时 ,就要用Stein算法。这个算法不需要除法和取模,而是采用整数的移位和最普通的加减法。

1.如果A=0,B是最大公约数,算法结束 
2.如果B=0,A是最大公约数,算法结束 
3.设置A1 = A、B1=B和C1 = 1 
4.如果An和Bn都是偶数,则An+1 =An /2,Bn+1 =Bn /2,Cn+1 =Cn *2(注意,乘2只要把整数左移一位即可,除2只要把整数右移一位即可) 
5.如果An是偶数,Bn不是偶数,则An+1 =An /2,Bn+1 =Bn ,Cn+1 =Cn (很显然啦,2不是奇数的约数) 
6.如果Bn是偶数,An不是偶数,则Bn+1 =Bn /2,An+1 =An ,Cn+1 =Cn (很显然啦,2不是奇数的约数) 
7.如果An和Bn都不是偶数,则An+1 =|An -Bn|,Bn+1 =min(An,Bn),Cn+1 =Cn 
8.n++,转4 

9. An+1和Bn+1相等,算法停止。最后的结果就是Cn+1和An+1的乘积。

上面的算法需要用到的两个性质为:

gcd(a,a) = a,也就是一个数和他自身的公约数是其自身 (也就是上面算法停止的条件就是An+1和Bn+1相等!!!!)
gcd(ka,kb) = k gcd(a,b),也就是最大公约数运算和倍乘运算可以交换,特殊的,当k=2时,说明两个偶数的最大公约数必然能被2整除 

一个奇数的所有约数都是奇数。

辗转相除法与Stein(更相减损术)的区别
(1)都是求最大公因数的方法,计算上辗转相除法以除法为主,更相减损术以减法为主,计算次数上辗转相除法计算次数相对较少,特别当两个数字大小区别较大时计算次数的区别较明显。
(2)从结果体现形式来看,辗转相除法体现结果是以相除余数为0则得到,而更相减损术则以减数与差相等而得到

class Championship {

public:

    vector<int> calc(int k) {

        // write code here

        //首先计算可能的分组情况

        int cou
4000
nt=1;

        int i=2*k-1;

        while(i>=1){

            count=count*i;

            i--;

            i--;

        }

        //下面计算从k+1个弱队中选出k-1个和强队相遇的情况

        int A=k-1;

        int B=k+1;

        int C=B-A;

        int sum=1;

        while(B>C){

            sum=sum*B;

            B--;

        }

        int sum2=1;

        while(A>=1){

            sum2=sum2*A;

            A--;

        }

        sum=sum/sum2;

        //下面计算把强弱进行配对的情况

        int count1=1;

        for(int j=k-1;j>=1;j--){

            count1=count1*j;

        }

        count1=count1*sum;

        vector<int> result;

        int fenzi=count-count1;

        int fenmu=count;

        //下面是求分子和分母的最大公约数

        int temp=gcd(fenzi,fenmu);

        if(temp!=0){

        fenzi=fenzi/temp;

        fenmu=fenmu/temp;

        }

        result.push_back(fenzi);

        result.push_back(fenmu);

        return result;

    }

   int gcd(int x,int y){

        

        int c=1;

        int minu;

            if(x==0&&y!=0)

               return y;

            if(x!=0&&y==0)

               return x;

            if(x==0&&y==0)

               return 0;

        while(x!=0&&y!=0){

            

            if(x%2==0&&y%2==0){

                x=x>>1;//不是x>>2!!!!!

                y=y>>1;

                c=c<<1;

            }

            else if(x%2==0&&y%2!=0){

                x=x>>1;

            }

            else if(x%2!=0&&y%2==0){

                y=y>>1;

            }

            else if(x%2!=0&&y%2!=0){    

                minu=x<y?x:y;

                x=abs(x-y);

                y=minu;

            }       

        }

        return(c*y);;//如果两个数没有最大公约数,那么就返回1

    }

};

需要注意的是上面的gcd求最大公约数的函数中,左移和右移可以代替乘以2和除以2,但是此时左移和右移仅仅移动一位不是两位!!!!

排列:从n个不同元素中,任取m(m≤n)个元素(被取出的元素各不相同),按照一定的顺序排成一列,叫做从n个不同元素中取出m个元素的一个排列。用符号 p(n,m)表示。
p(n,m)=n(n-1)(n-2)……(n-m+1)= n!/(n-m)!(规定0!=1)

排列与元素的顺序有关,组合与顺序无关。如231与213是两个排列,2+3+1的和与2+1+3的和是一个组合。当m=n时,为全排列

案例二:三只蚂蚁从正三角形的三个定点沿着边移动速度是相同的,问它们碰头的概率是多少?

如果方向并不都相同,则一定会相遇。

每只蚂蚁方向数有2种,一共有3只蚂蚁。

所以总的情况为2*2*2=8种。

只有完全顺时针和完全逆时针这3种情况下不相遇。

所以最后相遇的概率为:(8-2)/8=3/4;

class Ants {

public:

    vector<int> collision(int n) {

        // write code here

        vector<int> result;

        int count=n;

        int sum=1;

        while(count>0){

            sum=sum*2;

            count--;

        }

        //计算不碰头的概率

        

            sum=sum/2;

            result.push_back(sum-1);

            result.push_back(sum);

        return result;

    }

};

案例三:某地区重男轻女,一个家庭如果生出一个女孩就一直生,直到生出男孩就停止生育。假设一胎只出生一个孩子,问时间足够长后,男女比例是会变为多少?

假设这一地区一共有n个家庭,n/2的家庭第一胎就生出男孩,所以只有1个孩子。

有n/4的家庭先生1女孩,再生1男孩,有2个孩子。

有n/8的家庭先生2女孩,再生1男孩,有3个孩子。

孩子总数为:

n/2+(n/4)*2+(n/8)*3+(n/16)*4...=2*n(等比数列的计算公式Sn=a1(1-q^n)/(1-q))

每个家庭都会有一个男孩,所以2*n的孩子中,男孩数为n,所以女孩数也为n。所以比例依然是1:1。

案例四:给定给一个等概率随机产生1~5的随机函数,除此之外,不能使用任何额外的随机机制,请实现等概率随机产生1~7的随机函数。

1. 已经有等概率随机产生1,2,3,4,5的随机函数。

2.根据步骤1得到的结果减1,将得到f()-1>0 1 2 3 4 

3. f()*5->0,5,10,15,20

4. f()*5+(f()-1)>0,1,2,3,4......24

需要注意的是上面的两个f()是独立分别调用的,不要化简。第二个f()是用来填补前面的式子产生的缝隙。

5. 如果步骤4产生的数大于20,则重复进行步骤4,直到产生的结果在0~20之间。

6. 步骤5的结果将等概率随机产生0~20,所以步骤5的结果%7之后等概率产生0~6。(这是因为0~6模7会产生0~6,7~13模7也会得到0~6,14~20模7也会得到0~6!!!正好三套0~6.)

7. 步骤6的结果加1,将等概率产生1~7.

之所以通过上面的方式来产生随机数可以参考http://www.cnblogs.com/luxiaoxun/archive/2012/09/10/2678315.html,里面有详细的解释。

class Random5 {

public:
static int randomNumber();

};

class Random7 {

public:

    int rand5() {

        return Random5::randomNumber();

    }

    // 以上内容请不要修改

    

    int randomNumber() {

        // 代码写这里,通过rand5函数随机产生[1,7]

        int result=5*(rand5()-1)+rand5()-1;

        while(result>20){

            result=5*(rand5()-1)+rand5()-1;

        }

        result=result%7+1;

        return result;

    }

};

案例五;给定一个以p概率产生0,以1-p概率产生1的随机函数f(),p是固定的值,但你并不知道是多少,除此之外也不能使用任何额外的随机机制,请用f()实现等概率随机产生0和1的随机函数。

产生01和10序列的概率都为p*(1-p)

不断调用f,知道能够产生01或10,序列终止。

如果产生了01,返回0;

如果产生了10,则返回1即可。因为两个序列产生的概率是一样的。

class RandomP {

public:
static int f();

};

class Random01 {

public:

    // 用RandomP::f()实现等概率的01返回

    int random01() {

        while(1){

            int a=RandomP::f();

            int b=RandomP::f();

            if(a==0&&b==1){

                return 0;

            }

            if(a==1&&b==0){

                return 1;

            }

        }

        return 0;

    }

    //生成01以及10的概率是一样的,都是p(1-p)

    

};

案例六:假设函数f()等概率随机返回一个在[0,1)范围上的浮点数,那么在[0,x)区间上的数出现的概率为x(0<x<=1)、给定一个大于0的整数k,并且可以使用

f()函数,请实现一个函数依然返回在[0,1)范围上的数,但是在[0,x)区间上的数出现的概率为x的k次方。

1. 首先,如何把[0,x)范围上的数,从概率x调整为概率x的平方。

做法:调用两次f,返回较大的数即可。

2. 因此本题只要调用k次f,返回较大的数即可。

相当于进行k次伯努利实验,是相互独立的 !!!

class RandomSeg {

public:

    // 等概率返回[0,1)

    double f() {

        return rand() * 1.0 / RAND_MAX;

    }

    // 通过调用f()来实现

    double random(int k, double x) {

        //调用k次的f,最后返回最大值!!!

        int count=0;

        double max=-1;//因为这里生成的随机数是double类型的

        double rand;

        while(count<k){

            rand=f();//不能写多次的f(),写一次就是调用一次f()

            if(rand>max)

                max=rand;

            count++;

        }

        return max;

    }

};

上面的程序中一定更要注意double!!!!所以max以及rand都应该是double类型的!!!

案例七:给定给一个长度为N且没有重复元素的数组arr和一个整数M,实现函数等概率随机打印arr中的M个数。

解析:首先随机生成0~N-1之间的数然后打印,然后将这个数和数组最后一个元素交换位置,然后随机生成0~N-2之间的数打印,然后将这个数和数组倒数第二个元素交换位置,然后以此类推直到打印出M个数。

需要掌握的技巧:很多有关等概率的问题都可以利用和最后一个元素交换的做法!!!类似于抽签问题!!!

class RandomPrint {

public:

    vector<int> print(vector<int> arr, int N, int M) {

        // write code here

        int count=1;

        vector<int> result;

        int index;

        while(count<=M){

            index=rand()%N;//随机生成0-N-1之间的数

            result.push_back(arr[index]);

            arr[index]=arr[index]^arr[N-1];

            arr[N-1]=arr[index]^arr[N-1];

            arr[index]=arr[index]^arr[N-1];

            N--;

            count++;

        }

        return result;

    }

};

上面的算法中用异或来完成数组元素之间的交换位置!!

案例八:有一个机器按自然数序列的方式吐出球,1号球,2号球,等等。有一个袋子,袋子里最多只能装下k个球,并且除袋子以外,没有更多的空间。一个球一旦扔掉,就再也不可拿回。设计一种选择方式,使得当机器吐出第N号球的时候,袋子中的球数量是k个,同时可以保证从1号球到N号球中的每一个,被选进袋子的概率都是(k/n)。

解析:此题核心解法为蓄水池抽样算法,过程如下:

1. 处理1~k号球的时候,直接放进袋子里。

2. 处理第i号球时,以k/i的概率决定是否将第i号球放进袋子。如果不决定将第i号球放进袋子,直接扔掉第i号球。如果决定将第i号球放进袋子,那就从袋子里的k个球中随机扔掉一个,然后把第i号球放入袋子。

3. 处理第i+1号球时,重复步骤1或者步骤2.

但是,上面的过程是如何保证1~N号球每一个被选进袋子的概率都是(k/n)呢?下面是证明的过程:







综上,在i<=k时第i号球被放进袋子的概率也是k/N.。所以证明1~N号球每一个被选进袋子的概率都是(k/n).

class Bag {

public:
vector<int> ret;

    // 每次拿一个球都会调用这个函数,N表示第i次调用

    vector<int> carryBalls(int N, int k) {

        if(ret.size() < k){

               ret.push_back(N);  

        }

        else{

            //第N个球不放进袋子的概率为1-k/N

            if((rand()%N+1)<=k)//第N个球要放进袋子

            {

                int temp=rand()%k;//要删除的第temp个球,换成N

                ret[temp]=N;

            }

                

            //从袋子中随机选择一个数

            

        }

        

        return ret;

    }

};

上面的实现中,最难的是if((rand()%N+1)<=k),这个判断是说又(k/N)的概率执行这个if里面的内容!!!!

c++中如何随机生成0~1之间的浮点数,需要注意的是c++中rand()函数是随机生成一个整数!!rand()不需要参数,它会返回一个从0到最大随机数的任意整数,最大随机数的大小通常是固定的一个大整数。

C++中用来产生随机数的函数为rand(), 其返回值为整数。

而0到1之间的随机数,明显是浮点数。

这样无法
fee0
直接产生。

于是,必须经过转换。

转换思路为,生成一个0-N的随机数,然后对其除以N+1,则可以得到0-1直接的浮点型随机数。

N的确定,可以与要求的精度有关,比如需要三位小数,可以采用N=999,如需要四位,则采用N=9999,以此类推。

具体操作如下:
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: