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

概率的应用

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 count=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之间的随机数,明显是浮点数。

这样无法直接产生。

于是,必须经过转换。

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

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

具体操作如下:

1
2
3
4
5
6
7
8
9
10
#include <cstdlib>
#include <ctime>
void  get_rand( float  *p,  int  n) //函数功能为产生n个0-1的随机数,存储于数组p中。
{
     int  i;
     #define N 999  //三位小数。
     srand ( time (NULL)); //设置随机数种子,使每次获取的随机序列不同。
     for (i = 0; i < n; i ++)
         p[i] =  rand ()%(N+1)/( float )(N+1); //生成0-1间的随机数。
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值