字符串全排列與組合算法以及八皇后問題

【劍指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])。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章