题目:
输入一个字符串,打印出该字符串中字符的所有排列。例如输入字符串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);
}