【劍指offer-題38】
字符串排列
題目
輸入一個字符串,輸出該字符串的全排列。
思路
數學中的排列思想。算法步驟如下:
- 求出所有可能出現在第一個位置的字符。將首字符依次與後面的字符交換,即可達到此目的。
- 確定完第一個位置的字符之後,後面的字符作爲一個新的字符串,求其全排列,也就是遞歸算法。
- 將某個字符與首字符交換,求解完它的所有全排列之後,需要將首字符放回原位,方便首字符與後面字符繼續交換。
實現代碼
void SwitchToFront(char* pStr, char* pPos)
{
char chTemp = *pPos;
*pPos = *pStr;
*pStr = chTemp;
}
//pStr - 待求取全排列的字符串
//pStart - 當前遞歸的首字符位置
void PermutationReversely(char* pStr, char* pStart)
{
if (*(pStart + 1) == '\0')
{
cout << pStr << endl;
}
else
{
for (char *pCh = pStart; *pCh != '\0'; pCh++)
{
//將首字符與其後字符交換
SwitchToFront(pStart, pCh);
//增加首字符指針,遞歸遍歷後面的字符串
PermutationReversely(pStr, pStart + 1);
//將首字符迴歸原位,爲後續字符交換做準備
SwitchToFront(pStart, pCh);
}
}
}
//主函數
void Permutation(char* pStr)
{
if (!pStr || *pStr == '\0')
return;
PermutationReversely(pStr, pStr);
}
這裏會有一個問題,如果字符串中有相同的字符時,會出現相同的排列。所以,應該先檢查當前待交換字符之前是否已經出現過,如果出現過,則不進行任何處理,直接處理下一個字符。
修改後的代碼
//判斷當前字符是否已經出現過
bool CheckIfExist(char* pStr, const char* pCh)
{
char* p = pStr;
while (p != pCh)
{
if (*p == *pCh)
return true;
p++;
}
return false;
}
//pStr - 待求取全排列的字符串
//pStart - 當前遞歸的首字符位置
void PermutationReversely(char* pStr, char* pStart)
{
if (*(pStart + 1) == '\0')
{
cout << pStr << endl;
}
else
{
for (char *pCh = pStart; *pCh != '\0'; pCh++)
{
if (!CheckIfExist(pStart, pCh))
{
//將首字符與其後字符交換
SwitchToFront(pStart, pCh);
//增加首字符指針,遞歸遍歷後面的字符串
PermutationReversely(pStr, pStart + 1);
//將首字符迴歸原位,爲後續字符交換做準備
SwitchToFront(pStart, pCh);
}
}
}
}
字符串組合
題目
輸入一個字符串,輸出該字符串的所有組合。比如輸入abc,則輸出 a, b, c, ab, ac, bc, abc
思路
- 假設字符串的長度爲 n,那麼首先要確定需要找出長度爲 1~n的所有組合,採用循環。
- 假設當前確定的組合長度爲 m,那麼有兩種求法。第一,可以選擇首字符,然後在剩下的字符串中找出長度爲 m - 1的組合,可以採用 vector 容器保存當前選擇的字符;第二,不選擇首字符,直接在剩下的字符串中找出長度爲 m 的組合。採用遞歸方式。但應該注意,如果剩下的字符串的首字符之前已經出現過,則應該跳過此次遞歸,否則會重複出現。
- 判斷遞歸邊界條件。如果當前的字符串長度小於需要找到的組合長度,則應該直接返回。如果當前字符串的長度爲0,則說明長度爲 m 的組合尋找完畢,應該打印出 vector中的所有字符。
實現代碼
//判斷當前字符是否已經出現過
bool CheckIfExist(char* pStr, const char* pCh)
{
char* p = pStr;
while (p != pCh)
{
if (*p == *pCh)
return true;
p++;
}
return false;
}
//pStr - 當前需要確定組合的字符串
//pStart - 當前遞歸開始確定組合的字符串
//length - 需要確定的組合長度
//strVec - 保存的之前已經確定的字符
void PermutationReversely(char* pStr, char *pStart, int length, vector<char>& strVec)
{
if (strlen(pStart) < length)
return;
if (!length)
{
for (int i = 0; i < strVec.size(); i++)
{
cout << strVec[i];
}
cout << endl;
return;
}
strVec.push_back(*pStart);
PermutationReversely(pStr, pStart + 1, length - 1, strVec);
strVec.pop_back();
//當下一個開始的字符串在之前沒有出現時,纔會遞歸
if (!CheckIfExist(pStr, pStart + 1))
{
PermutationReversely(pStr, pStart + 1, length, strVec);
}
}
//主函數
void Permutation(char* pStr)
{
if (!pStr || *pStr == '\0')
return;
int maxLength = strlen(pStr);
vector<char> strVec;
for (int length = 1; length <= maxLength; length++)
{
PermutationReversely(pStr, pStr, length, strVec);
}
}
八皇后問題
問題
在 8 乘 8 的國際象棋上拜訪8個皇后,要求每個皇后不能處於同一行,不能處於同一列,且不能位於同一對角線,問有多少種擺放方法。
解題思路
這就是一個排列問題。
- 每個皇后不能處於同一行,那麼一定每一行都有一個皇后。所以定義一個數組 ColunmIndex[8],ColumnIndex[i]表示第 i 行皇后的列座標。
- ColumnIndex[8] 初始化爲{0, 1, 2, 3, 4, 5 ,6, 7}。這樣它們就滿足了不位於同一行,和同一列
- 最終的解就是 ColumnIndex 數組的所有全排列中,滿足皇后不位於同一對角線的,即滿足:abs(i - j) != abs(ColumnIndex[i] - ColumnIndex[j])。