点:理解全排列,递归的运用
题意:如字符串abcd,获取其全排列就是:abcd、abdc、acbd、acdb、adcb、adbc、
bacd、badc、bcad、bcda、bdca、bdac、
cbad、cbda、cabd、cadb、cdba、cdab、
dbca、dbac、dcba、dcab、dacb、dabc
共24种
剑指offer面试题28
思路:全排列是一个较常见的题,表面感觉不特别难以理解,但实际做到真正理解和运用还是比较困难的:
简单直观的想,abc的全排列,abc、acb、bac、bca、cab、cba,就是字母a加上字母bc的两个组合bc和cb,字母b加上ac的组合ac和ca,字母c加上ab的组合ab和ba
这就是:从简单例子分析出规律,这点在面试时非常重要,因为很多使用动态归划、递归、以及一些需要洞察出其规律的题都是这样,必须要洞察到规律,然后才能选择正确的道路继而写的正确,如何洞察规律?就必须从简单的例子中分析出;
如这个全排列,由上面的简单例子的结果"字母a加上字母bc的两个组合bc和cb,字母b加上ac的组合ac和ca,字母c加上ab的组合ab和ba",发现一种规律是:
依次以字符串的每个字母为前缀,加上其余字母的排列组合,就是全排列。
而后面每个字母的排列组合,也同样是一个全排列问题。
再具体的看,abc,首先a做前缀,然后是获取bc的全排列,包括bc和cb,然后b做全排列。。。然后c做全排列;
大致一个算法的规律是:每个字母做前缀,然后后面的子串也递归的这样去干,直到当前字符串结束为止,然后递归回来继续,比如abc,
1、a做前缀去找bc的全排列
1.1、b做前缀找c的全排列,就是c,输出一个abc
1.2、b和c交换,c做前缀找b的全排列,就是b,输出一个acb
2、b做前缀找ac的全排列
.............
3、c做前缀找ab的全排列
.............
如果是更复杂的abcd呢?对于上面的第1步,a做前缀找bcd的全排列,bcd的全排列的找法和abc的找法是一样的。
至此基本明确了找的大致思路,并且很适应于递归,递归的停止条件是什么?就是上面的第1步的b和c交换后,发现没有可交换的余地了。那么递归不断入栈的参数是?是当前准备要和其后面字符串的字符们不断交换的字符的位置,想象abcde的交换过程,假设在b和c交换后是acbde,位置是b的索引是2,接下来马上就是b还要和b、d、e交换,这3个交换的递归过程,分别需要位置参数2、3、4。到e再递归时,也就是索引为4时再继续递归时,就到了递归停止条件了,这时候设计为输出当前字符串的时候。
代码很老套,关键还是理解思路。
全排列也是很多其他面试题的至少结果正确的解法。
每个字符串全排列有多少种情况?一个字符是一种,两个字符是2个 *1 = 2种,3个字符是3个 * f(2) = 6种,4个字符是4个 * f(3) = 24种,也就是1*2*3*......N种,N是字符串长度
代码:
#include <iostream>
void swap (std::string &str, int i, int j) {
if (i == j) {
return;
}
char t = str[i];
str[i] = str[j];
str[j] = t;
}
void perm (std::string &raw, int idx, const int size) {
if (idx == size) {
std::cout << raw << "\t";
} else {
for (int i = idx; i < size; i++) {
swap(raw, i, idx);
perm(raw, idx + 1, size);
swap(raw, i, idx);
}
}
}
int main () {
std::string raw = "abcde";
perm(raw, 0, raw.length());
std::cout << std::endl;
return 0;
}