面试题 28(*),字符串的排列(排列问题的典型解法:采用递归,每次交换首元素和剩下元素中某一个的位置)
想了10多分钟才有一个思路,整体上和答案一致,也是递归,子问题为提出首字符后,子字符串的排列。但是繁琐很多。
不过我比答案考虑得周全了些,拿到这道题的第一反应,是询问字符串中如果有重复的字符,在输出排列时 这两个字符交换是否属于一种情况。书中没有考虑到这种情况。
我的思路:顺序遍历整个字符串中的每一个字符,遍历过程中,如果当前字符和之前遍历过任何一个字符都不同,就将它提出,放到前面,提出此字符后剩下的字符串变成一个新的字符串。继续调用原函数处理这个新的字符串,直到新的字符串长度为0,就将之前提出的字符组成的字符串打印出来。
定义函数rePermutate(char *preSub, char *endSub)。preSub字符串用来存放每次被提到前面的字符,endSub存放提取后剩下的字符串。
考虑到字符串是以char *的方式存储,也就是说,是存在一段内存上,每次递归都是访问同一个字符串,为了防止递归将原字串打乱,我让每次递归都新开了空间将新的字符串复制出来。又因为,遍历字符串的每个字符的过程中,需要判断这个字符是否和已经被处理过的字符相同,我由定义了map来存放处理过的字符。
这样做的坏处就是新开了太多空间来存储字符。
伪代码如下:
/** 仅仅是伪代码 */ void rePermutate(char *preSub, char *endSub){ if(str(endSub)) == 0{ print preSub; return; } char character = NULL; for(i = 0; i< strlen(endSub); i++){ character = endSub[i]; map<char, int> mp; if(!alReadyDealt(character, mp)){ //把字符并到preSub后面 char *newPreSub = new char[strlen(preSub) + 1]; strcpy(newPreSub, preSub); strcat(newPreSub, character); //处理提取字符后剩下的字符串 char *newEndSub = new char[strlen(endSub) - 1]; for(j = 0;j < i;j++) newEndSub[j] = endSub[i]; for(j = i+1;j < strlen(endSub);j++) newEndSub[j] = endSub[j]; rePermutate(newPreSub, newEndSub); } mp.clear(); delete mp; } } bool alReadyDealt(char *character, HashMap &mp){ if(mp[character] == NULL){ mp[character] = 1; return true; }else{ return false; } }
书中的方法更加节省空间和时间,它采用的方法是:
遍历字符串,然后将当前遍历的字符和首字符交换位置,调用本函数处理首字符之后的子字符串,函数执行完后,再调换回来。
在不需要复制字符串的情况下,避免了每次递归函数相互之间的影响。
将书中的代码,再加上我用map存储已处理过字符的思路,综合起来如下:
// StringPermutation.cpp : Defines the entry point for the console application. // // 《剑指Offer——名企面试官精讲典型编程题》代码 // 著作权所有者:何海涛 #include "stdafx.h" #include <map> void Permutation(char* pStr, char* pBegin); void Permutation(char* pStr) { if(pStr == NULL) return; Permutation(pStr, pStr); } void Permutation(char* pStr, char* pBegin) { if(*pBegin == '\0') { printf("%s\n", pStr); } else { std::map<char, int> mp; for(char* pCh = pBegin; *pCh != '\0'; ++ pCh) { if(mp[*pCh] == NULL){ mp[*pCh] = 1; char temp = *pCh; *pCh = *pBegin; *pBegin = temp; Permutation(pStr, pBegin + 1); temp = *pCh; *pCh = *pBegin; *pBegin = temp; } } mp.clear(); } } // ====================测试代码==================== void Test(char* pStr) { if(pStr == NULL) printf("Test for NULL begins:\n"); else printf("Test for %s begins:\n", pStr); Permutation(pStr); printf("\n"); } int main() { Test(NULL); char string1[] = ""; Test(string1); char string2[] = "a"; Test(string2); char string3[] = "aba"; Test(string3); char string4[] = "abc"; Test(string4); return 0; }
推广:
这种类型的题目推广开来就是:求一个元素组中,中所有满足一定条件的排列。
对于这种问题,在没有很好的剪枝的前提下,往往采用先求出所有排列,然后挨个试验是否满足条件。
求所有排列的方法,就是上面所使用的 交换元素->递归->换回来 的思路。
类似的题目还有八皇后问题,求所有八皇后的排放方式。
思路:皇后必然是每列每行一个,因此我们用一个数组int column[8],里面存放0-7这8个整数。column[3] = 7代表第4列皇后放在第8行。然后求这个column数组的全排列,每个排列,验证是否有皇后在斜线上,即 column[i] - column[j] == i - j
全排列的求解方式,就是从第一个元素开始,交换元素->递归->换回来。
附:
Permutation是"排序"的意思,计算机高频词。
The permutation is even if the number of inversions it contains is even.
如果一个排列中含有的反序的个数是偶数,则称它为偶排列。
------------------------------------------------
Felix原创,转载请注明出处,感谢博客园!
posted on 2014-01-26 05:24 Felix Fang 阅读(1479) 评论(0) 编辑 收藏 举报