• 面试试题 一 (排列组合)


    昨天有个同学去面试了,回来讨论关于面试的题,因为是提前批次的,所以有点难,有点绕,自己写总结了一下:

    1.此题是有关24点游戏,(整体的实现在http://www.cnblogs.com/liuamin/p/6947028.html),其中用到了递归,比较重要的知识点是排列组合,针对此题,简单说就是4(n)个元素,按不同的排列组合方式,有4!(n! 也就是A(n,n))种方式。都说迭代的人,递归的是神,先利用递归进行实现:(自己觉得代码很简单明了)

    #include<iostream>
    #include<vector>
    #include<string>
    using namespace std;
    void print(vector<string> &arr)                           //只是简单打印每次输出的结果
    {
        for (const auto c : arr)
            cout << c << " ";
        cout << endl;
    }
    void perm(size_t t, vector<string> &arr, int &cnt)    //核心算法,进行排列组合,t从0开始,cnt计算有多少排列方式
    {
        if (t >= arr.size()) 
        {
            print(arr);
            cnt++;
            return;
        }
        for (size_t i = t; i < arr.size(); i++)
        {
            if (i != t )
               swap(arr[i], arr[t]);
            perm(t + 1,arr,cnt);   //表示对剩下的元素进行排列组合,perm{b,c,d}/ perm{a,c,d}/ perm{b,a,d}/ perm{b,c,a}
            if (i != t)
               swap(arr[i], arr[t]);   //表示交换之后再交换回来,要在原有的基础上(a,b,c,d)进行交换
        }
    }
     
    int main()               //测试
    {
        static int cnt=0;
        vector<string> arr{ "a", "b" , "c", "d"};
        perm(0,arr,cnt);
        cout << "共有" << cnt <<"种排列组合方法!"<< endl;
        return 0;
    }

          这里详细讲解下有关递归方法实现原理:n个元素的排列组合表示为perm{x1,x2,x3....,xn},当有4个元素(a, b, c, d)时,则表示为perm{a, b, c, d},按递归的方法则perm{a,b,c,d}=a.perm{b,c,d}+b.perm{a,c,d}+c.perm{b,a,d}+d.perm{b,c,a},  这里加号表示并集的意思,对于为什么是c.perm{b,a,d}而不是c.perm{a,b,d},从代码中swap()函数可以得到它是交换了a和c,同理d.perm{b,c,a}也是交换了a和d。

        代码中有两个swap()函数,是因为总是在原有的组合上(a,b,c,d)进行排列交换,具体的过程为:第一轮:开始i=t=0,不交换,也就是a.perm{b,c,d}(递归对b,c,d进行排列组合),执行完后,不交换;第二轮:交换a和b(swap(arr[1],arr[0])),所以为b. perm{a,c,d}(执行递归),之后再交换回来(swap(arr[1],arr[0]))则恢复a,b,c,d;第三轮:交换a和c(swap(arr[2],arr[0]),是在a,b,c,d的基础上进行交换(第三轮已经恢复回来了)),所以为c.perm{b,a,d}(执行递归),之后再交换回来(swap(arr[2],arr[0]))为a.b.c.d;第四轮:交换a和d (swap(arr[3],arr[0]),也是在a,b,c,d的基础上进行的交换),为d.perm{b,c,a}(执行递归),之后再交换回来(swap(arr[3],arr[0])),依然为a,b,c,d,因此执行完整个代码,输入的vector<string> arr依然是保持不变的(为a,b,c,d)。

    perm{a,b,c,d}=a.perm{b,c,d}+b.perm{a,c,d}+c.perm{b,a,d}+d.perm{b,c,a}

     =a.b.perm{c,d}+a.c.perm{b,d}+a.d.perm{c,b}+b.a.perm{c,d}+b.c.perm{a,d}+b.d.perm{c,a}+c.b.perm{a,d}+c.a.perm{b,d}+c.d.perm{a,b}+d.b.perm{c,a}+d.c.perm{b,a}+d.a.perm{c,b}

    = a.b.c.d+a.b.d.c+a.c.b.d+a.c.d.b+a.d.c.b+a.d.b.c+

       b.a.c.d+b.a.d.c+b.c.a.d+b.c.d.a+b.d.c.a+b.d.a.c+

       c.b.a.d+c.b.d.a+c.a.b.d+c.a.d.b+c.d.a.b+c.d.b.a+

       d.b.c.a+d.b.a.c+d.c.b.a+d.c.a.b+d.a.c.b+d.a.b.c 

    验证后和代码运行结果一致的。。

    2 .简单迭代实现,穷举法,虽然迭代看起来逻辑简单,但是时间复杂度很高,当待排列的数目发生变化时,这种方法要改变程序for循环的层数。(程序没有一般性)

    #include<iostream>
    using namespace std;
    /*这里用迭代实现的排列组合,因为有四个数,所以有四层迭代,也就是说每个位置的数都可以为1,2,3,4 ,但是4个位置同时不能有相等的数出现*/
    /*缺点是当待排列组合的数越来越多,则时间复杂度会很高为n^4*/
    void perm(int arr[],int &cnt)   //这里有四层循环
    {
        for (int i = 0; i < 4; i++)
        {
            for (int j = 0; j < 4; j++)
            {
                if (j == i)       //i不能等于j
                    continue;
                for (int k = 0; k < 4; k++)
                {
                    if (k == i || k == j)   //k不能等于i,也不能等j
                        continue;
                    for (int l = 0; l < 4; l++)
                    {
                        if (l==i||l==j||l==k)
                            continue;
                        cout << arr[i] << ' ' << arr[j] << ' ' << arr[k] << ' ' << arr[l] << endl;
                        cnt++;
                    }
                }                
            }    
        }        
    }
    int main()
    {
        int arr[4] = { 1, 2, 3, 4 };
        static int cnt = 0;
        perm(arr,cnt);
        cout << "共有" << cnt << "种方法!" << endl;
        return 0;
    }

    3.对第二种迭代方法进行优化,循环变为三层,循环的次数正好等于排列组合的方法数。(也是待排序的数目发生变化时要改变程序,程序也没有一般性)

    #include<iostream>
    using namespace std;
    /*这里用迭代实现的排列组合,对上面的迭代方法进行优化,能减少时间复杂度*/
    /*但是仍然是当待排列组合的数越来越多,则时间复杂度会变高(为排列组合的方法数,这里为4!=24)*/
    void perm(int arr[], int &cnt)   
    {
        for (int i = 0; i < 2; i++)   
        {
            for (int j = 0; j < 3; j++)
            {
                for (int k = 0; k < 4; k++)  //当求余4时,结果都是在0,1,2,3之间,刚好为数组的下标
                {
                    cout << arr[k % 4] << ' ' << arr[(k + j % 3 + 1) % 4] << ' ';
                    cout << arr[(k + (j + i % 2 + 1) % 3 + 1) % 4] << ' ';
                    cout << arr[(k + (j + (i + 1) % 2 + 1) % 3 + 1) % 4] << endl;
                      //arr[j%3]+" "+arr[(j+i%2+1)%3]+" "+arr[(j+(i+1)%2+1)%3]    3个数的情况
                    cnt++;
                }
            }
        }
    }
    int main()
    {
        int arr[4] = { 0, 1, 2, 3 };
        static int cnt = 0;
        perm(arr, cnt);
        cout << "共有" << cnt << "种排列组合方法!" << endl;
        return 0;
    }

     4,当从一个数组中取m个数字,有多少种取法,利用穷举法,这里假设从一个数组中取3个数字。(此处为C(8,3),与上面不同的是取出来的数字不讲究顺序,也就是C(n,m),从n个元素中取m个元素有多少种取法(m<=n)),程序没有一般性。

    #include<iostream>
    #include<vector>
    using namespace std;
    /*穷举法*/
    void combination(const vector<int> & arr, int &cnt)      //这里是三层循环,是从数组中取三个数,要是取4个数,程序就改成4层循环
    {
        for (size_t i = 0; i < arr.size() - 2; i++)          //直接改为i<arr.size()也可以,因为j=i+1,k=j+1,且小于arr.size(),均有限定
        {
            for (size_t j = i + 1; j < arr.size() - 1; j++)    //直接改为i<arr.size()也可以,原因同上
            {
                for (size_t k = j + 1; k < arr.size(); k++)
                {
                    cout << arr[i] << ' ' << arr[j] << ' ' << arr[k] << endl;
                    cnt++;
                }
            }
        }
    }
    int main()
    {
        vector<int> arr { 1, 2, 3, 4,5,6,7,8};
        static int cnt = 0;
        combination(arr, cnt);
        cout << "共有" << cnt << "种组合方法!" << endl;
        return 0;
    }

    5、对于问题4,在n个元素中取m个元素共有C(n,m)中取法,利用递归实现。

    #include<iostream>
    #include<vector>
    using namespace std;
    /*递归法,从n个元素中取m个元素,有C(n,m)种取法,这里index[]存放取出来的m个元素;k为要取得元素个数,开始为m,后来为m-1,m-2,...,1;
    m为取出元素的个数,作用是打印取出的元素数组index;size为总元素个数n ; cnt统计多少种组合方式*/
    void combine(vector<int> &arr, int index[], int k, const int m, int size,int &cnt)  
    {  
        for (int i = size - 1; i >= k - 1; --i)  
        {  
            index[k - 1] = arr[i];  
            if (k > 1)              //当取出的元素个数为1时,则不再递归
            {  
                combine(arr, index, k - 1, m, i,cnt);  
            }  
            else  
            {  
                for(int i = 0; i < m; ++i)  
                    cout << index[i]; 
                cnt++;
                cout << endl;  
            }  
        }  
    }  
    void combination(vector<int> &arr, int size, int m,int & cnt)  
    {  
        if(m > size)  
            return;  
        
        int* out = new int[m];  
        combine(arr,out,m,m,size,cnt);  
        delete [] out;  
    }  
    
    int main()
    {
        vector<int> arr {1,2,3,4,5,6,7,8};
        static int cnt = 0;
        combination(arr, arr.size(),3,cnt);   //这里是取3个元素
        cout << "共有" << cnt << "种组合方法!" << endl;
        return 0;
    }

    例如从5个元素{1,2,3,4,5}取3个元素,则取一个元素后,问题简化为从剩下的取2个元素,再取走一个元素后,则简化为从剩下的元素中取一个元素。

    具体过程为:这里是从最后一个元素开始,例如从5个元素{1,2,3,4,5}取3个元素:则从i=size-1=4开始(因为下标都是从零开始),i>=2,k为3,输出的index[2]=5,

    再递归从{1,2,3,4}中取2个元素i=3开始,i>=1,k为2,输出index[1]=4,

    再递归从{1,2,3}中取一个元素,i=2开始,i>=0,k为1,此时是指取出一个元素,不再递归,index[0]=3/2/1,则依次输出3,4,5; 2,4,5; 1,4,5;

    返回至上一层--i为2,从{1,2,3,4}中取出2个元素,此时index[1]=3,递归下去,同理从{1,2}中取一个元素,则不再递归,依次输出2,3,5; 1,3,5;

    再返回上一层--i为1,从{1,2,3,4}中取出2个元素,此时index[1]=2,递归下去,同理从{1}中取一个元素,不再递归,输出1,2,5;

    同理,index[2]=4时,index[1]=3输出为2,3,4;1,3,4;index[1]=2,输出1,2,4;index[2]=3时,index[1]=2输出为1,2,3。

    补充:6 . 对问题1,要从n个元素中选择m个元素,也就是有A(n,m)中排列方法,在1的基础上修改:

    #include<iostream>
    #include<vector>
    using namespace std;
    void perm(size_t t, vector<int> &arr,const int m, int &cnt)     //核心算法,进行排列组合,t从0开始,m为取出的元素个数,cnt计算有多少排列方式,
    {
        if (t >= m) 
        {
            for (int j=0;j!=m;j++)
              cout << arr[j]<< " ";
            cnt++;
            cout << endl;
            return;
        }
        for (size_t i = t; i < arr.size(); i++)
        {
            if (i != t )
               swap(arr[i], arr[t]);
            perm(t + 1,arr,m,cnt);
            if (i != t)
               swap(arr[i], arr[t]);                      //表示交换之后再交换回来,要在原有的基础上进行交换
        }
    }
     
    int main()               //测试
    {
        static int cnt=0;
        vector<int> arr{1,2,3,4,5};
        perm(0,arr,3,cnt);  //这里是取3个元素
        cout << "共有" << cnt <<"种排列组合方法!"<< endl;
        return 0;
    }
  • 相关阅读:
    python 类的自定义属性
    好的設計模式
    sql server 过滤重复数据
    sql server 2000 sp3
    css
    SQL server 与Oracle开发比较(同事帮忙整理的,放这里方便查询)
    深圳 帮部门招聘人才
    BCP等三個sql server 過程
    CTE and CONNECT BY 樹的查詢(轉)
    继承System.Web.UI.Page的页面基类
  • 原文地址:https://www.cnblogs.com/liuamin/p/6940342.html
Copyright © 2020-2023  润新知