(全排列)数组的全排列问题

  • 问题一:https://www.nowcoder.com/practice/f0069cfcd42649e3b6b0c759fae8cde6?tpId=46&tqId=29148&tPage=3&rp=3&ru=/ta/leetcode&qru=/ta/leetcode/question-ranking

    • 这个题目意思是给定一个排列数组,然后要求出下一个排列的数组。比如说1234->1243    1243->1324等等。这个题目是一个全排列的题目,但是只是要求出后一个排列。STL容器中有一个next_permutation函数是来求出数组的所有排列的。

    • STL里面的next_permutation算法的主要思路:在当前序列中,从尾端往前寻找两个相邻元素,前一个记为*i,后一个记为*ii,并且满足*i < *ii。然后再从尾端寻找另一个元素*j,如果满足*i < *j,即将第i个元素与第j个元素对调,并将第ii个元素之后(包括ii)的所有元素颠倒排序,即求出下一个序列了。

    • next_permutation函数测试代码
      • #include <iostream>
        #include <algorithm>
        #include <vector>
        using namespace std;
        int main(){
            vector<int> str;
            for(int i=1; i<5; i++)
                str.push_back(i);
          //这里我是默认的将输入数组变成了升序排序,如果要求全排列必须重新排序。
            sort(str.begin(), str.end());
        while(next_permutation(str.begin(), str.end())){ for(int i=0; i<4; i++) cout<<str[i]; cout<<endl; } }
      • 测试结果

         

    • 好回到这个题目:主要思路和这个函数的解法是类似的,给出代码
      class Solution {
      public:
          void nextPermutation(vector<int> &num) {
              int size = num.size();
              if (size < 2)
                  return ;
              int i, j;
              for (i=size-2; i>=0; --i)//这个是从后往前找到相邻的两个数,其中第i个数小于第i+1个数
                  if (num[i] < num[i+1])
                      break;
              for (j=size-1; j>i; --j)//这个是从后找到一个大于第i个数的j
                  if (num[j] > num[i])
                      break;
              if (i>=0) swap(num[i], num[j]);//交换第i个数和第j个数
              reverse(num.begin()+i+1, num.end());//再将第i+1后面的所有数字进行翻转,就形成了下一个序列
          }
      };
  • 问题二:https://www.nowcoder.com/practice/4bcf3081067a4d028f95acee3ddcd2b1?tpId=46&tqId=29133&tPage=1&rp=1&ru=/ta/leetcode&qru=/ta/leetcode/question-ranking

  • 这个题目是求全排列的问题,其实只要稍微处理一下上面那个求后一个排列的函数,让他来个bool类型的返回值,(这里有一个重点:必须要是升序数组哦)。这样就可以实现了。
  • 代码:
    class Solution {
    public:
        bool nextPermutation(vector<int> &num) {
            int size = num.size();
            if (size < 2)
                return false;
            int i, j;
            for (i=size-2; i>=0; --i)//这个是从后往前找到相邻的两个数,其中第i个数小于第i+1个数
                if (num[i] < num[i+1])
                    break;
            for (j=size-1; j>i; --j)//这个是从后找到一个大于第i个数的j
                if (num[j] > num[i])
                    break;
            if (i>=0) swap(num[i], num[j]);//交换第i个数和第j个数
            reverse(num.begin()+i+1, num.end());//再将第i+1后面的所有数字进行翻转,就形成了下一个序列
                
            return i>=0;
        }
        vector<vector<int> > permute(vector<int> &num) {
            vector<vector<int> > res;
            sort(num.begin(), num.end());//这里是求全排列,所以我们需要将数组重新排序才能得出
               do{
                res.push_back(num);
            }while(nextPermutation(num));
            return res;    
        }
    };

     

  • 问题三:用C++写一个函数, 如 Foo(const char *str), 打印出 str 的全排列,如 abc 的全排列: abc, acb, bca, dac, cab, cba
    • 方法一:全排列的递归实现(不包含重复的元素)

      • 思路:为方便起见,用123来示例下。123的全排列有123、132、213、231、312、321这六种。首先考虑213和321这二个数是如何得出的。显然这二个都是123中的1与后面两数交换得到的。然后可以将123的第二个数和每三个数交换得到132。同理可以根据213和321来得231和312。因此可以知道——全排列就是从第一个数字起每个数分别与它后面的数字交换。找到这个规律后,递归的代码就很容易写出来了:
      • 代码:
            #include<iostream>
            using namespace std;
            #include<assert.h>
            #include <stdio.h>
        
            void Permutation(char* pStr, char* pBegin)
            {
                assert(pStr && pBegin);
        
                if(*pBegin == '\0')
                    printf("%s\n",pStr);
                else
                {
                    for(char* pCh = pBegin; *pCh != '\0'; pCh++)
                    {
                        swap(*pBegin,*pCh);
                        Permutation(pStr, pBegin+1);
                        swap(*pBegin,*pCh);
                    }
                }
            }
        
            int main(void)
            {
                char str[] = "abc";
                Permutation(str,str);
                return 0;
            }
    • 方法二:去掉重复元素的全排列的递归实现。

      • 思路:由于全排列就是从第一个数字起每个数分别与它后面的数字交换。我们先尝试加个这样的判断——如果一个数与后面的数字相同那么这二个数就不交换了。如122,第一个数与后面交换得212、221。然后122中第二数就不用与第三个数交换了,但对212,它第二个数与第三个数是不相同的,交换之后得到221。与由122中第一个数与第三个数交换所得的221重复了。所以这个方法不行。

        换种思维,对122,第一个数1与第二个数2交换得到212,然后考虑第一个数1与第三个数2交换,此时由于第三个数等于第二个数,所以第一个数不再与第三个数交换。再考虑212,它的第二个数与第三个数交换可以得到解决221。此时全排列生成完毕。
        这样我们也得到了在全排列中去掉重复的规则——去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。下面给出完整代码:
      • 代码:
            #include<iostream>  
            using namespace std;  
            #include<assert.h>  
              
            //在[nBegin,nEnd)区间中是否有字符与下标为pEnd的字符相等  
            bool IsSwap(char* pBegin , char* pEnd)  
            {  
                char *p;  
                for(p = pBegin ; p < pEnd ; p++)  
                {  
                    if(*p == *pEnd)  
                        return false;  
                }  
                return true;  
            }  
            void Permutation(char* pStr , char *pBegin)  
            {  
                assert(pStr);  
              
                if(*pBegin == '\0')  
                {  
                    static int num = 1;  //局部静态变量,用来统计全排列的个数  
                    printf("第%d个排列\t%s\n",num++,pStr);  
                }  
                else  
                {  
                    for(char *pCh = pBegin; *pCh != '\0'; pCh++)   //第pBegin个数分别与它后面的数字交换就能得到新的排列     
                    {  
                        if(IsSwap(pBegin , pCh))  
                        {  
                            swap(*pBegin , *pCh);  
                            Permutation(pStr , pBegin + 1);  
                            swap(*pBegin , *pCh);  
                        }  
                    }  
                }  
            }  
              
            int main(void)  
            {  
                char str[] = "baa";  
                Permutation(str , str);  
                return 0;  
            }  
    • 方法三就是前面的非递归实现,大体思路是和上面的一样的,实现不一样而已。


  • 问题四:输入两个整数n和m,从数列1,2,3...n中随意取几个数,使其和等于m,要求列出所有的组合。
    • 思路:
      • 1、问题其实本质上就是0/1背包问题,对于每一个n,我们采用贪婪策略,先考察是否取n,如果取n,那么子问题就变成了find(n-1,m-n),而如果舍弃n,子问题则为find(n-1,m)。

        2、那么,如何制定解的判定策略?我们知道,递归需要边界条件,而针对背包问题,边界条件只有两种,如果n<1或者m<1,那么便相当于“溢出”,无法combo出m,而另一种可能就是在剩余的n个里恰好满足m==n,即此时 背包刚好填充满,输出一组解单元。除此之外,再无其他。

        注:我们设置flag背包,用来标注对应的n+1是否被选中,1表示被选中,0则表示未选中,每当满足m==n时,则输出一组解。程序容易产生逻辑bug的地方在于length的使用(读者可以思考一下为何需要全局变量length,而不是直接使用n来代替for循环)。

    • 代码
      #include <stdio.h>
      #include <stdlib.h>
      #include <string.h>
      
      int length;
      
      void findCombination(int n,int m,int *flag)
      {
          if(n < 1 || m < 1)
              return;
          if(n > m)
              n = m;
          if(n == m)
          {
              flag[n-1] = 1;
              for(int i=0;i<length;i++)
              {
                  if(flag[i] == 1)
                      printf("%d\t",i+1);
              }
              printf("\n");
              flag[n-1] = 0;
          }
          flag[n-1] = 1;
          findCombination(n-1,m-n,flag);
          flag[n-1] = 0;
      
          findCombination(n-1,m,flag);
      }
      
      int main()
      {
          int n, m;
          scanf("%d%d",&n,&m);
          length = n;
          int *flag = (int*)malloc(sizeof(int)*length);
          findCombination(n,m,flag);
          free(flag);
          return 0;
      }

       

posted @ 2017-01-23 15:13  Kobe10  阅读(1107)  评论(0编辑  收藏  举报