递归---排列问题

来源:互联网 发布:云计算细分市场格局 编辑:程序博客网 时间:2024/06/06 10:09

一、排列问题

    设R={r1,r2,...,rn}是要进行排列的n个元素,Ri=R-{ri}。集合x中元素的全排列记为Perm(X)。

    (ri)Perm(X)表示在全排列Perm(X)的每一个排列前加上前缀ri得到的排列。

    R的全排列可归纳如下:

    当n=1时,Perm(R)=(r),其中r是集合中唯一的元素;

    当n>1时,Perm(R)由(r1)Perm(R1),(r2)Perm(R2),(r3)Perm(R3)......(rn)Perm(Rn)构成。

实现思想:将数组中所有的数都与第一个数交换,这样就总是求后n-1个数的全排列(其实,就是把数组中每一个数都分别放到第一个位置,然后求剩下n-1个数的全排列)

示例

当n=3,并且E={a,b,c},则:
perm(E)=a.perm({b,c}) + b.perm({a,c}) + c.perm({a,b})
perm({b,c})=b.perm(c) + c.perm(b)
a.perm({b,c})=ab.perm(c) + ac.perm(b)=ab.c + ac.b=(abc, acb)


依递归定义,设计Perm(R)的递归算法如下:

#include <iostream>using namespace std;template <class Type>inline void Swap(Type &a,Type &b);template <class Type>void Perm(Type list[],int k,int m);int main(){    int list[3];    for(int i=0; i<3;i++)    {        list[i] = i+1;    }    Perm(list,0,2);    return 0;}template <class Type>inline void Swap(Type &a,Type &b){    Type temp = a;    a = b;    b = temp;}template <class Type>void Perm(Type list[],int k,int m){    //只剩下一个元素    if(k == m){        for(int i=0; i<=m; i++)        {            cout<<list[i]<<" ";        }        cout<<endl;    }    else    {        //将list[k:m}中的每一个元素分别与list[k]中的元素交换        //然后递归计算list[k+1:m]的全排列,将计算结果作为list[0:k]后缀        for(int i=k; i<=m;i++){            Swap(list[k],list[i]);            Perm(list,k+1,m);            Swap(list[k],list[i]);        }    }}


leetcode题目


class Solution {    vector<vector<int>> result;public:    void Perm(vector<int>& nums,int k,int n){        if(k>=n) return;        if(k==n-1){            /*for(int i=0;i<n;++i) cout<<nums[i];            cout<<endl;*/            result.push_back(nums);        }        else{            for(int i=k;i<n;++i){                swap(nums[k],nums[i]);                Perm(nums,k+1,n);                swap(nums[k],nums[i]);            }        }            }    vector<vector<int>> permute(vector<int>& nums) {//nums中数字不同,没有重复的        int n=nums.size();        if(n<=0) return result;        Perm(nums,0,n);        return result;    }};

当数组中有重复的元素时:leetcode题目2


class Solution {    vector<vector<int>> result;public:    void Perm1(vector<int> nums,int i,int j){        if(i==j-1){            result.push_back(nums);            return;        }        else{            for(int k=i;k<j;k++){                if(k!=i&&nums[k]==nums[i]) continue;                swap(nums[k],nums[i]);                Perm1(nums,i+1,j);            }        }    }    vector<vector<int>> permuteUnique(vector<int>& nums) {        sort(nums.begin(),nums.end());        Perm1(nums,0,nums.size());        return result;    }};
leetcode题目:下一个全排列


class Solution {public:    void nextPermutation(vector<int>& nums) {        int n=nums.size();        if(n<=1) return;        int k=-1;        for(int i=n-2;i>=0;i--){            if(nums[i]<nums[i+1]){//从后往前,找到第一个非递增的数的下标k,例:1,2,5,4,3,3,则找到2,下标为1                k=i;                break;            }        }        if(k==-1){//整个数组为非递增            reverse(nums.begin(),nums.end());            return;        }        int l=-1;        for(int j=n-1;j>k;j--){//从后往前,找到第一个大于之前找到的那个数nums[k]            if(nums[j]>nums[k]){                l=j;                break;            }        }        swap(nums[k],nums[l]);        reverse(nums.begin()+k+1,nums.end());    }};

以上这个算法思想:

1.从右往左寻找连续递增序列,例:1,3,5,4,2,其中5,4,2为递增序列

2.从上述序列中找一个比它前面的数(3)大的最小的数(4),并且交换这两个数,1,3,5,4,2--->1,4,5,3,2,交换后的依然是递增序列

3.新的递增序列逆序,1,4,5,3,2--->1,4,2,3,5


leetcode题目:全排列中第k个排列


康托展开的公式:(不用记,看形势就行,下面会有例子)

X=an*(n-1)!+an-1*(n-2)!+...+ai*(i-1)!+...+a2*1!+a1*0!

ai为整数,并且0<=ai<i(1<=i<=n)

适用范围:没有重复元素的全排列

来自点击打开链接

第一类题:N个数的第k个排序,例子,1,2,3,4共有4!种排列,1234,1243,1324等等。按顺序应该是

1234

1243

1324

1342

1423

1432等等

可以通过STL中next_permutation(begin, end);来算下一个全排列,理论上你要算n个数的第k个排列只要调用k-1次next_permutation()就行,但是一般来说肯定会超时的,因为next_permutation的时间复杂度是O(n)(如果自己写出来next_permutation时间复杂度比n大就要注意了,其中一个容易疏忽的地方是最后排序可以用reverse而不是sort)。所以如果用这个的话时间复杂度是O(N^2)。

而用康托展开只要O(n)就行,下面来说说具体怎么做:

题目:找出第16个n = 5的序列(12345)

首先第十六个也就是要前面有15个数,要调用15次next_permutation函数。

根据第一行的那个全排列公式,15 / 4! = 0 …15  =>  有0个数比他小的数是1,所以第一位是1

拿走刚才的余数15,用15 / 3! = 2 …3   =>  剩下的数里有两个数比他小的是4(1已经没了),所以第二位是4

拿走余数3, 用 3 / 2! = 1 …1   => 剩下的数里有一个数比他小的是3,所以第三位是3

拿走余数1, 用 1/  1! = 1 …0    =>  剩下的数里有一个数比他小的是 5(只剩2和5了),所以第四位是5


所以排列是 1,4,3,5,2


第二类题:已知是n = 5,求14352是它的第几个序列?(同一道题)

用刚才的那道题的反向思维

第一位是1,有0个数小于1,即0* 4!

第二位是4,有2个数小于4,即2* 3!

第三位是3,有1个数小于3,即1* 2!

第四位是5,有1个数小于5,即1* 1!

第五位是2,不过不用算,因为肯定是0

所以14352是 n = 5的第 0 + 12 + 2 + 1 + 0 = 15    + 1(求的是第几个,所以要加一) = 16

第16个,跟刚才那道题一样,证明对了



class Solution {public:    string getPermutation(int n, int k) {//康托展开的公式        string res;        string nums = "123456789";//因为已知n:1~9        int f[10] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};//0!,1!,2!,......,9!        --k;//需要k-1次        for (int i = n; i >= 1; --i) {            int j = k / f[i - 1];            k %= f[i - 1];            res.push_back(nums[j]);            nums.erase(nums.begin() + j);        }        return res;    }};
ps:

把一个问题映射到二进制数,有很多好玩的题目:


有1000个一模一样的瓶子,其中有999瓶是普通的水,有一瓶是毒药。任何喝下毒药的生物都会在一星期之后死亡。现在,你只有10只小白鼠和一星期的时间,如何检验出哪个瓶子里有毒药?
      1. 将所有瓶子编号,1、2、3、... 、1000;

  2. 将所有编号转换成对应二进制数, 0000000001,0000000010,0000000011,...,1111101000;

 

  3. 给1号小白鼠吃所有二进制数最低位为1的药,如,1、3、5、7、...

         给2号小白鼠吃所有二进制数中,次低位为1的药,如,2、3、4、6、...

                ......

   给10号小白鼠,吃所有二进制数中,右数第10位为1的瓶子对应的药,如,512、513、514、...

  4. 最后,根据死去的小白鼠就可以推断出是哪瓶为毒药,如,第2、4、7、9个小白鼠死了,那么对应的二进制数为0101001010,即,第660瓶 为毒药

(有1000瓶老鼠药和10只老鼠,其中一瓶有毒,老鼠喝了两天会挂,如何在两天之内找到哪瓶有毒:

老鼠排成一列,每只老鼠看成一位bit,1~1000转化为二进制,每瓶都给位为1的对应的老鼠喝,最后挂掉的老鼠的位置1,其余置0,对应的号就是那瓶有毒的药瓶的号)


原创粉丝点击