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

题目:

输入一个字符串,打印出该字符串中字符的所有排列。例如输入字符串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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值