排列组合

来源:互联网 发布:window的端口共有多少 编辑:程序博客网 时间:2024/06/05 07:32

概率组合题吗分类:

1. 以高中数学为基础的古典概率计算方法。

2. 斐波那契数和卡特兰数。

3. 以选择题居多。


案例一:在6*9的方格中,以左上角为起点,右下角为终点,每次只能向下走或者向右走,请问一共有多少种不同的走法。

解法:一共走13步,其中必然有五步向下,剩下的八步向右。



class Robot {
public:
    int countWays(int x, int y) {
        // write code here
        //一共有x-1次向右走,有y-1次向左走
        int c=x+y-2;
        x=x-1;
        y=y-1;
        int d=c-x+1;
        int sum=1;
        while(c>=d){
            sum=sum*(c--);
        }
        while(x>=1){
            sum=sum/(x--);
        }
        return sum;
        
        
        
    }
};

注意:排列组合的公式只需要写成正常的数学表达式然后用循环等语句就可以实现。


案例二:ABCDEFG七人站队,要求A必须在B的左边,但是不要求一定相邻,请问一共有多少种排法?

第二问,如果要求A必须在B左边,并且一定更要相邻,请问一共有多少种排法?





class StandInLine {
public:
    vector<int> getWays(int n, int a, int b) {
        // write code here
        vector<int> result;
        int temp=n;
        int result1=1;
        while(temp>=1){
            result1=result1*temp;
            temp=temp-1;
        }
        result1=result1/2;
        result.push_back(result1);
        temp=n-1;
        result1=1;
        while(temp>=1){
            result1=result1*temp;
            temp=temp-1;
        }
        result.push_back(result1);
        return result;
    }
};

案例三:六个人排成一排,要求甲与乙不相邻,并且甲与丙不相邻的排法数是多少?


其中,甲与乙相邻的总数包括甲乙相邻并且甲在乙的前面,也包括甲乙相邻,并且乙在甲的前面这两种情况。

这两种情况的和就是甲乙相邻的总数。同理,甲丙相邻的总数也是如此计算的,结果也是一样的。

但是需要注意的是,甲乙相邻与甲丙相邻它们是有交集的,交集就是乙甲丙或者丙甲乙出现的时候,所以最后的结果要加上一个这种情况。

class LonelyA {
public:
    int getWays(int n, int A, int b, int c) {
        // write code here
        //a和b相邻的数量
        int result1=1;
        int temp=n-1;
        while(temp>=1){
            result1=result1*temp;
            temp=temp-1;
        }
        result1=result1*2;//因为a和b有前有后两种情况
        //a和c相邻的数量也是result1
        //但是上面的两种情况包括了两个bac以及cab的情况
        int result2=1;
        temp=n-2;
        while(temp>=1){
            result2=result2*temp;
            temp=temp-1;
       }
        result2=result2*2;//因为包括了两种情况,bac以及cab
        temp=n;
        int result3=1;
        while(temp>=1){
            result3=result3*temp;
            temp=temp-1;
        }
        result3=result3-result1*2+result2;
        return result3;
    }
};



案例四;10颗相同的糖果,分给3个人,每人至少一颗,问有多少种分法?


class Distribution {
public:
    int getWays(int n, int m) {
        // write code here
        //m个人也就是有m-1个隔板
        //因为每人至少一颗,所以隔板不能放在两端。
        //因此又n颗糖果,就有n-1个缝隙
        //因此就是从n-1个缝隙中找m-1个位置
        int A=m-1;
        int B=n-1;
        int C=B-A;
        int result1=1;
        while(A>=1){
            result1=result1*A;
            A=A-1;
        }
        int result2=1;
        
        while(B>C){
            result2=result2*B;
            B=B-1;
        }
        return (result2/result1);
        
        
    }
};


案例五:十个不同的球放入三个不同的桶里,一共有多少种方法:

这道题和上面的题不同之处在于球和桶都是不同的,所以不是一个简单的拆分问题。



案例六:有10颗糖,如果每天至少吃一颗,吃完为止,问一共有多少种不同的吃法?

这个为题和上面的拆分问题类似,都是可以用隔板解决的,


二项式定理的内容如下:


案例七:假设有n对左右括号,请求出合法的排列有多少个?合法是指每一个括号都可以找到与之配对的括号,比如n=1时,()是合法的,但是)(为不合法。

左括号数量为n,右括号数量为n,总排列数为。不合法的排列一定是出现了右括号比左括号多一个的前缀。

对于(,用1表示;

对于),用-1表示;

当第一次出现-1多于1的个数时,代表第一次出现右括号比左括号多的情况,也就是不合法情况。对于这种情况,

将这个不合法的前缀中的1变成-1,-1变成1,最后就会得到有n+1个1,和n-1个-1组成的排列。!!!

接下来可以证明,每一个非法的排列通过如上的变换方式,都可以得到n+1个1和n-1个-1所组成的排列!!!原因在于:

这道题规定的就是一共有2n个括号,并且其中左括号和右括号的数量是相等的!!只不过是排列的顺序不同会导致产生不合法

的排列!!因此找到第一个非法的序列并把其中的1以及-1进行调整,最后一定会得到n+1个1和n-1个-1.

所以有下面的定理:每一个非法排列经过变换和变换的逆过程可以生成一个唯一的n+1个1和n-1个-1组成的排列,而倒过来也是一样的,也就是说每一个n+1个1和n-1个-1组成的排列都可以还原出一个唯一的一个非法排列(还原方式就是找到第一个左括号比右括号多的前缀,然后将左括号编程右括号,右括号编程左括号!!!和上面的那个唯一的区别就是这里是找左括号比右括号第一个多的前缀,而上面的是找第一个右括号比左括号多的前缀!!!)。也就是说这两个是一一对应的。

证明过程:

已知有n对括号,也就是n个左括号和n个右括号,那么第一个不合法的前缀中,假设左括号有x个,那么右括号一定比左括号多一个,所以为x+1个。那么,

此时在非法前缀以外的序列中,就有n-x个左括号以及n-(x+1)个右括号。那么如果此时对不合法前缀进行变换,将左括号变成右括号,右括号变成左括号,那么非法前缀

中就有x个右括号,x+1个左括号,此时,整个序列中就有n-x+x+1=n+1个左括号,和n-(x+1)+x=n-1个右括号。所以上面的结论是正确的。


所以不合法的排列数=(n+1)个1和(n-1)个-1组成的排列数=

那么合法的排列数为

。这个公式很重要 ,一定更要记住!!

class Parenthesis {
public:
    int countLegalWays(int n) {
        // write code here
        //直接使用卡特兰数就可以了!!
        int temp=2*n;
        int temp1=n;;
        int result=1;
        while(temp>n){
            result=result*temp;
            temp--;
        }
        while(temp1>=1){
            result=result/temp1;
            temp1--;
        }
        result=result/(n+1);
        return result;
    }
};


案例八:n个数进出栈的顺序有多少种?假设栈的容量无限大。

解析:这道题和上面的题相似。进栈相当于左括号,出栈相当于右括号。所以解法与案例七相同。出栈一定更要保证栈中是有元素的。所以进栈的一定要大于等于出栈的。出入站的顺序数量就是合法的括号序列数量。和上面的结果是相同的!!

class Stack {
public:
    int countWays(int n) {
        // write code here
        //出栈的话必须保证栈中要有元素!!!将进栈操作看成是左括号,出栈操作看成是右括号
        //所以左括号一定更要大于等于右括号!!因此,也是一个卡特兰数问题
        int result=1;
        for(int i=2*n;i>n;i--){
            result=result*i;
        }
        for(int i=1;i<=n+1;i++){
            result=result/i;
        }
        return result;
    }
};

在补充一个问题,2n个人排队买票,n个人拿5块钱,n个人拿10块钱,票价是5块钱1张,每个人买一张票,售票员手里没有零钱,问有多少种排队方法让售票员可以顺利卖票。

解析:这道题也是类似的。5块钱相当于左括号,10块钱相当于右括号。答案还是卡特兰数。5块钱的数量一定更要大于等于10块钱。

class BuyTickets {
public:
    int countWays(int n) {
        // write code here
        //一定更要保证拿5块的人比拿10块的人多!!
        //因此这也是一个卡特兰数问题!
        //需要注意的是,卡特兰数问题似乎都有一个共性:2n,n个人.....,另外n个人......
        int result=1;
        int temp1=2*n;
        int temp2=n+1;
        while(temp1>n){
            result=result*temp1;
            temp1--;
        }
        while(temp2>=1){
            result=result/temp2;
            temp2--;
        }
        return result;
    }
};

案例九:求n个无差别的节点构成的二叉树有多少种不同的结构?

假设n个无差别的节点构成不同的结构数为f(n),f(0)表示空树,所以规定种数为1种,也就是f(0)=1;

然后将n个节点依次作为树的头部,进行归纳:

第一个节点作为头部,它的左子树为空,也就是结构种数为1;右子树有2到n也就是n-1个节点,作为1节点的右子树,种数就是f(n-1)。所以把第一个节点当做头的情况下,

种数就是1*f(n-1);

第二个节点作为头部,它的左子树只有一个节点,也就是结构种数为1;右子树有3到n也就是n-2个节点,作为2节点的右子树,种数就是f(n-1)。所以把第二个节点当做头的情况下,种数就是1*f(n-2);

以此类推,下面的分别为

f(2)*f(n-3);

f(3)*f(n-4);

......

最后得出的结论如下:


上面的是卡特兰公式的第二种形式!!!!!!!

class TreeCount {
public:
    int countWays(int n) {
        // write code here
        //这里也是卡特兰数
        int result=1;
        for(int i=2*n;i>n;i--){
            result=result*i;
        }
        for(int i=1;i<=n+1;i++){
            result=result/i;
        }
        return result;
    }
};


案例九:12个高矮不同的人,排成两排,每排必须是从矮到高排列,而且第二排比对应第一排的人高,问排列方式有多少种?

解析:先将12个人从矮到高排序!!!


对于上面的例子来说,如果有前缀1比0个数多的话,说明必然会出现不合法的情况。此时

第二排的人多了,所以以后一定过会出现第一排的人比第二排的人高!!!

因此,问题变为任意一个前缀,不能出现1比0多的情况,所以依然是卡特兰数问题。

class HighAndShort {
public:
    int countWays(int n) {
        // write code here
        //这里面总数已经是n,不是2*n!!!!!!
        int A=n;
        int B=n/2;
        int result=1;
        for(int i=A;i>B;i--){
            result=result*i;
        }
        for(int j=B+1;j>=1;j--){
            result=result/j;
        }
        return result;
    }
};

但是对于上面的实现来说,总数就是n,并不是2*n!!!!!!!!


案例十:有n个信封,包含n封信,现在把信拿出来,再装回去,要求每封信不能装回它原来的信封,问有多少种装法?

一开始我以为是卡特兰数问题:卡特兰数应用
          考虑半天,但是也无法列出卡特兰数的公式。安静下来发现,h(0)=h(1)=0,不满足卡特兰数条件。于是乎就想请教大神。大神给了个思路:
       计算f(n),假设1,2,3,4,......n。那么n若放在位置i,可以分两种情况考虑。i放在位置n和i不放在位置n。为什么这么考虑呢?因为如果i放在n,那么剩下就是f(n-2),否则的话就是f(n-1)。第一种情况你很容易理解,i和n都放好了,剩下的就是n-2个数不放在原来的信封里。但是i如果不放在n位置,剩下n-1个数,其中i放哪都可以啊,和题目中每个信封不放在原信封条件不同啊。很好,i是可以放在任何位置,但前提假设了这种情况是i不放在n位置。这样这n-1个数,除了i,其余的限制是不放在原信封,i的限制是不放在n位置。所以等价这其实就是f(n-1)。
        问题到这,用递归计算就太简单了。当然也可以改用循环来解决。综上,可以得出下面的结论:
对于n封信按照题目要求的装法即为f(n):
假设第n封信放入了第i个信封
情况一:第i封信也放入了第n个信封中,后序为f(n-2).
情况二:第i封信没放入了第n个信封中,后序为f(n-1).
n封信放入i个信封,i的选择有(n-1)种,
所以总数为f(n)=(n-1)*(f(n-1)+f(n-2)).
class CombineByMistake {
public:
    int countWays(int n) {
        // write code here
        //这里面主要是递归公式的使用
        long last=1;
        long nlast=0;
        if(n==2)
            return last;
        if(n<2)
            return nlast;
        long temp;
        for(int i=3;i<=n;i++){
            temp=last;
            last=(i-1)*(nlast+last)%1000000007;
            nlast=temp;
        }
        return last;
    }
};
需要注意的是上面因为有mod,所以一定更要用long类型!!!!否则结果会发生错误!!



原创粉丝点击