目錄
一、題目:字串的排列
1.1 全排列的遞迴實現(不考慮有字元重複)
1.1.1 寫法1
1.1.2 寫法2
1.2 全排列的遞迴實現 (考慮有字元重複)
1.3 全排列的非遞迴實現
二、字串的組合
2.1 方法1
2.2 方法2: 用位運算來實現求組合
三. 字串全排列擴充套件----八皇后問題
四. 相關題目 參考
同劍指offer第二版: "面試題: 38"
為方便起見,用123來示例下。123的全排列有123、132、213、231、312、321這六種。首先考慮213和321這二個數是如何得出的。顯然這二個都是123中的1與後面兩數交換得到的。然後可以將123的第二個數和每三個數交換得到132。同理可以根據213和321來得231和312。因此可以知道——全排列就是從第乙個數字起每個數分別與它後面的數字交換。找到這個規律後,遞迴的**就很容易寫出來了:
#includeusing namespace std;
#includevoid permutation(char* pstr, char* pbegin) }}
int main(void)
//k表示當前選取到第幾個數的索引,m表示最後乙個數的索引
void permutation(char* pstr,int k,int m)
else }
} int main(void)
如果字串中有重複字元的話,上面的那個方法肯定不會符合要求的,因此現在要想辦法來去掉重複的數列。
由於全排列就是從第乙個數字起每個數分別與它後面的數字交換。我們先嘗試加個這樣的判斷——如果乙個數與後面的數字相同那麼這二個數就不交換了。如122,第乙個數與後面交換得212、221。然後122中第二數就不用與第三個數交換了,但對212,它第二個數與第三個數是不相同的,交換之後得到221。與由122中第乙個數與第三個數交換所得的221重複了。所以這個方法不行。
換種思維,對122,第乙個數1與第二個數2交換得到212,
然後考慮第乙個數1與第三個數2交換,此時由於第三個數等於第二個數,所以第乙個數不再與第三個數交換。
再考慮212,它的第二個數與第三個數交換可以得到解決221。此時全排列生成完畢。
這樣我們也得到了在全排列中去掉重複的規則——去重的全排列就是從第乙個數字起每個數分別與它後面非重複出現的數字交換。下面給出完整**:
#includeusing namespace std;
#include//在[nbegin,nend)區間中是否有字元與下標為pend的字元相等
bool isswap(char* pbegin , char* pend)
return true;
}void permutation(char* pstr , char *pbegin)
else
} }}
int main(void)
ok,到現在我們已經能熟練寫出遞迴的方法了,並且考慮了字串中的重複資料可能引發的重複數列問題。那麼如何使用非遞迴的方法來得到全排列了?
要考慮全排列的非遞迴實現,先來考慮如何計算字串的下乙個排列。如"1234"的下乙個排列就是"1243"。只要對字串反覆求出下乙個排列,全排列的也就迎刃而解了。
如何計算字串的下乙個排列了?來考慮"926520"這個字串,我們從後向前找第一雙相鄰的遞增數字,"20"、"52"都是非遞增的,"26 "即滿足要求,稱前乙個數字2為替換數,替換數的下標稱為替換點,再從後面找乙個比替換數大的最小數(這個數必然存在),0、2都不行,5可以,將5和2交換得到"956220",然後再將替換點後的字串"6220"顛倒即得到"950226"。
對於像「4321」這種已經是最「大」的排列,採用stl中的處理方法,將字串整個顛倒得到最「小」的排列"1234"並返回false。
這樣,只要乙個迴圈再加上計算字串下乙個排列的函式就可以輕鬆的實現非遞迴的全排列演算法。按上面思路並參考stl中的實現原始碼,不難寫成乙份質量較高的**。值得注意的是在迴圈前要對字串排序下,可以自己寫快速排序的**(請參閱《白話經典演算法之六 快速排序 快速搞定》),也可以直接使用vc庫中的快速排序函式(請參閱《使用vc庫函式中的快速排序函式》)。下面列出完整**:
#include#include#includeusing namespace std;
#include//反轉區間
void reverse(char* pbegin , char* pend)
//下乙個排列
bool next_permutation(char a)
}reverse(a , pend); //如果沒有下乙個排列,全部反轉後返回false
return false;}
int cmp(const void *a,const void *b)
int main(void)
while(next_permutation(str));
return 0;
}
至此我們已經運用了遞迴與非遞迴的方法解決了全排列問題,總結一下就是:
上面我們詳細討論了如何用遞迴的思路求字串的排列。同樣,本題也可以用遞迴的思路來求字串的組合。
假設我們想在長度為n的字串中求m個字元的組合。我們先從頭掃瞄字串的第乙個字元。針對第乙個字元,我們有兩種選擇:第一是把這個字元放到組合中去,接下來我們需要在剩下的n-1個字元中選取m-1個字元;第二是不把這個字元放到組合中去,接下來我們需要在剩下的n-1個字元中選擇m個字元。這兩種選擇都很容易用遞迴實現。下面是這種思路的參考**:
#include#include#includeusing namespace std;
#includevoid combination(char *string ,int number,vector&result);
void combination(char *string)
void combination(char *string ,int number , vector&result)
if(*string == '\0') //返回條件2:到達字串結尾時
return ;
result.push_back(*string);
combination(string + 1 , number - 1 , result); //若選擇第1個,選擇後面m-1個
result.pop_back(); //清空結果,即還原到上乙個狀態
combination(string + 1 , number , result); //若不選擇第1個,選擇後面m個}
int main(void)
由於組合可以是1個字元的組合,2個字元的字元……一直到n個字元的組合,因此在函式void combination(char* string),我們需要乙個for迴圈。另外,我們用乙個vector來存放選擇放進組合裡的字元。
#includeusing namespace std;
int a = ;
char str = "abcde";
void print_subset(int n , int s)
bool check(int columnindex , int length)
}return true;
}void permutation(int columnindex , int length , int index)
}else
}} void print(int columnindex , int length)
int main(void)
輸入兩個整數n和m,從數列1,2,3...n中隨意取幾個數,使其和等於m,要求列出所有的組合。
#include #include using namespace std;
listlist1;
void find_factor(int sum,int n)
面試題28 字串排列
輸入乙個字串,按字典序列印出該字串中字元的所有排列。例如輸入字串abc,則列印出由字元a,b,c所能排列出來的所有字串abc,acb,bac,bca,cab和cba。結果請按字母順序輸出。輸入描述 輸入乙個字串,長度不超過9 可能有字元重複 字元只包括大小寫字母。1 class solution 1...
面試題28 字串的排列
1.輸入乙個字串,列印出該字串中字元的所有排列,例如輸入字串abc,則列印出字元a,b,c的所有可能排列,abc,acb,bac,bca,cab,cba.分析 我們可以講字串看成兩部分組成,第一部分為第乙個字元,第二部分是後面所有的字元。首先求所有可能出現在第一位置的字元,即把第乙個字元和後面所有的...
面試題25 字串的排列
題目 輸入乙個字串,列印出該字串中字元的所有排列。例如輸入字串abc,則列印由字元a,b,c所能排列出來的所有字串 abc,abc,bac,bca,cab,cba 我們求整個字串的排列,可以看成兩步 首先求出所有可能出現在第乙個位置的字元,即把第乙個字元和後面所有的字元交換。下圖就是分別把第乙個字元...