剑指Offer28字符串的排列(递归和非递归实现)扩展有重复元素的排列,字符串的组合种类

来源:互联网 发布:2017优化重组数学答案 编辑:程序博客网 时间:2024/05/16 02:56

题目:

输入一个字符串,打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出有字符abc所能排列出来的所有字符串,abc、acb、bac、bca、cab、cba

分析:

这是一个典型的全排列的问题,那么就是典型的深搜递归的题。可以这么分析:求整个字符串的排列,可以看成两部:首先求所有可能出现的第一个位置的字符,即把第一个字符和后面所有的字符字符交换。第二步,固定第一个字符,求后面所有字符的排列。这个时候我们仍把后面的所有字符分成两部分:后面字符的第一个字符,以及这个字符之后的所有字符。然后把第一个字符逐一和它后面的字符交换。

void Permutation(char *pStr){    if(pStr == NULL)        return ;    Permutation(pStr,pStr);}void Permutation(char *pStr,char *index){    if(*index == '\0')    {        printf("%s\n",pStr);        return ;    }    else    {        for(char *ch = index;*ch != '\0';++ch)        {            char temp = *ch;            *ch = *index;            *index = temp;            Permutation(pStr,index+1);            temp = *ch;            *ch = *index;            *index = temp;        }    }}

当然,我们也可以用非递归的思路来解决这个问题,这里有一种求解方法,我是从网上看到的,觉得非常经典。下面来看它的思路

只要求出当时全排列的下一个排列,此后以此类推就可以了。那么怎么求下一个排列,这里给了一种方法,我们先来看一下。“926520”这个字符串,我们从后面向前看,先看“20”从左往右看并不是递增的,那么再往前看,“52”,也不是递增的,再往前看“65”,还不是再往前看,“26”此时是递增的了,那么定位到2,然后从最后一个开始遍历,找到第一个比2大的数,我们看到0比2小,那么往前找,2等于2,再往前看5比2大,将5和2交换,得到”956220“然后我们将从6开始往后的字符串翻转,得到“950226”,那么这个就是下个字符串。

看懂了转化过程,我们就能很轻松的写出代码来了。

void Reverse(char *pBegin,char *pEnd){    while(pBegin < pEnd)        swap(*pBegin++,*pEnd--);}bool Next_Permutation(char *pStr){    int length = strlen(pStr);    char *index = pStr+(length-1);    char *End = pStr+(length-1);    ///如果是空串    if(*index == *pStr)        return false;    while(*index != *pStr)    {        char *index_next = index;        index --;        if(*index < *index_next)        {            char *real = End;            while(*real < *index)                real--;            swap(*index,*real);            ///将替换之后的所有数翻转            Reverse(index_next,End);            return true;        }    }    Reverse(pStr,End);    return false;}

那么我接下来扩展一下这个题,如果说这个题目中要求说有重复元素,那么它的排列怎么来求呢?

其实我们可以分析一下。

比如字符串是“122”,根据上面咱们的思路应该是第二个数和第三个数交换,我们会发现还是“122”,也就是重复了,所以我们应该避免出现这种情况。那么我们现在应该这个考虑,找到要被替换的那个位置,一直往后直到那个替换的数的位置,这期间如果有和替换的数一样的话,那么这次就不替换。比如当前字符串“212”,那么它的下一个字符串应该是1和第三个2交换,这期间没有和1一样的数,那么就可以交换,但是当下一次交换的时候,就应该是第一2和第二个2交换了,这俩数一样,所以这趟的交换就可以免了。

下面看代码:

bool IsSwap(char *pBegin,char *End){    char *sh;    for(sh = pBegin;sh < End;sh++)    {        if(*sh == *End)            return false;    }    return true;}void PermutationChongfu(char *pStr,char *index){    if(*index == '\0')    {        printf("%s\n",pStr);        return ;    }    else    {        for(char *ch = index;*ch != '\0';ch++)        {            if(IsSwap(index,ch))            {                swap(*index,*ch);                PermutationChongfu(pStr,index+1);                swap(*index,*ch);            }        }    }}

字符串的组合问题:

输入三个字符abc,他们的组合有a b c ab ac  bc  abc

这里可以把这些字符串的组合用地归来处理

这样分析:

这一堆字符串,先从中找到一个,那么接下来就有两种处理方式,1是我可以把它放进我的容器里,然后再从剩下的n-1里拿出m-1个。2是我不将其放进容器,那么接下来就要在剩下的n-1里面找出m个数放进容器了。

代码可以这么实现:

void Combination(char *pStr){    if(pStr == NULL)        return ;    int length = strlen(pStr);    vector<char> result;    for(int i = 1;i<=length;i++)        Combination(pStr,i,result);}void Combination(char *pStr,int num ,vector<char> &result){    if(num == 0)    {        vector<char>::iterator iter = result.begin();        for(;iter != result.end();iter++)            printf("%c ",*iter);        cout<<endl;        return ;    }    if(*pStr == '\0')        return ;    result.push_back(*pStr);    Combination(pStr+1,num-1,result);    result.pop_back();    Combination(pStr+1,num,result);}

题目扩展:

题目:

输入两个整数n和m,从数列1,2,3...n中随意取几个数,使其和等于m,要求列出所有的组合。

分析:

其实这个题前面的题很像,从后面开始遍历,如果把这个数加进容器,那么整个数的和就减去这个数,并且让这个数减1,如果不加进容器的话,那么和是不变的,只要把数减去1就行了。

下面看代码:

void find_factor(int sum,int n){    if(sum < 0 || n < 0)        return ;    if(sum == n)    {        result.reverse();        list<int>::iterator iter = result.begin();        for(;iter != result.end();iter++)            printf("%d ",*iter);        cout<<n<<endl;        result.reverse();    }    result.push_back(n);    find_factor(sum-n,n-1);    result.pop_back();    find_factor(sum,n-1);}


0 0
原创粉丝点击