題目:輸入一個字符串,打印出該字符串中字符的所有排列。
例如輸入字符串abc,則輸出由字符a、b、c 所能排列出來的所有字符串
abc、acb、bac、bca、cab 和cba。
分析:此題最初整理於去年的微軟面試100題中第53題,第二次整理於微軟、Google等公司非常好的面試題及解答[第61-70題] 第67題。無獨有偶,這個問題今年又出現於今年的2011.10.09百度筆試題中。ok,接下來,咱們先好好分析這個問題。
- 一、遞歸實現
從集合中依次選出每一個元素,作爲排列的第一個元素,然後對剩餘的元素進行全排列,如此遞歸處理,從而得到所有元素的全排列。以對字符串abc進行全排列爲例,我們可以這麼做:以abc爲例
固定a,求後面bc的排列:abc,acb,求好後,a和b交換,得到bac
固定b,求後面ac的排列:bac,bca,求好後,c放到第一位置,得到cba
固定c,求後面ba的排列:cba,cab。代碼可如下編寫所示:
- template <typename T>
- void CalcAllPermutation_R(T perm[], int first, int num)
- {
- if (num <= 1) {
- return;
- }
- for (int i = first; i < first + num; ++i) {
- swap(perm[i], perm[first]);
- CalcAllPermutation_R(perm, first + 1, num - 1);
- swap(perm[i], perm[first]);
- }
- }
- void Permutation(char* pStr, char* pBegin);
- void Permutation(char* pStr)
- {
- Permutation(pStr, pStr);
- }
- void Permutation(char* pStr, char* pBegin)
- {
- if(!pStr || !pBegin)
- return;
- if(*pBegin == '\0')
- {
- printf("%s\n", pStr);
- }
- else
- {
- for(char* pCh = pBegin; *pCh != '\0'; ++ pCh)
- {
- // swap pCh and pBegin
- char temp = *pCh;
- *pCh = *pBegin;
- *pBegin = temp;
- Permutation(pStr, pBegin + 1);
- // restore pCh and pBegin
- temp = *pCh;
- *pCh = *pBegin;
- *pBegin = temp;
- }
- }
- }
void Permutation(char* pStr, char* pBegin);
void Permutation(char* pStr)
{
Permutation(pStr, pStr);
}
void Permutation(char* pStr, char* pBegin)
{
if(!pStr || !pBegin)
return;
if(*pBegin == '\0')
{
printf("%s\n", pStr);
}
else
{
for(char* pCh = pBegin; *pCh != '\0'; ++ pCh)
{
// swap pCh and pBegin
char temp = *pCh;
*pCh = *pBegin;
*pBegin = temp;
Permutation(pStr, pBegin + 1);
// restore pCh and pBegin
temp = *pCh;
*pCh = *pBegin;
*pBegin = temp;
}
}
}
- 二、字典序排列
把升序的排列(當然,也可以實現爲降序)作爲當前排列開始,然後依次計算當前排列的下一個字典序排列。
對當前排列從後向前掃描,找到一對爲升序的相鄰元素,記爲i和j(i < j)。如果不存在這樣一對爲升序的相鄰元素,則所有排列均已找到,算法結束;否則,重新對當前排列從後向前掃描,找到第一個大於i的元素k,交換i和k,然後對從j開始到結束的子序列反轉,則此時得到的新排列就爲下一個字典序排列。這種方式實現得到的所有排列是按字典序有序的,這也是C++ STL算法next_permutation的思想。算法實現如下:
- template <typename T>
- void CalcAllPermutation(T perm[], int num)
- {
- if (num < 1)
- return;
- while (true) {
- int i;
- for (i = num - 2; i >= 0; --i) {
- if (perm[i] < perm[i + 1])
- break;
- }
- if (i < 0)
- break; // 已經找到所有排列
- int k;
- for (k = num - 1; k > i; --k) {
- if (perm[k] > perm[i])
- break;
- }
- swap(perm[i], perm[k]);
- reverse(perm + i + 1, perm + num);
- }
- }
2、跳臺階問題
題目:一個臺階總共有n 級,如果一次可以跳1 級,也可以跳2 級。
求總共有多少總跳法,並分析算法的時間複雜度。
分析:在九月騰訊,創新工場,淘寶等公司最新面試十三題中第23題又出現了這個問題,題目描述如下:23、人人筆試1:一個人上臺階可以一次上1個,2個,或者3個,問這個人上n層的臺階,總共有幾種走法?咱們先撇開這個人人筆試的問題(其實差別就在於人人筆試題中多了一次可以跳三級的情況而已),先來看這個第27題。
首先考慮最簡單的情況。如果只有1級臺階,那顯然只有一種跳法。如果有2級臺階,那就有兩種跳的方法了:一種是分兩次跳,每次跳1級;另外一種就是一次跳2級。
現在我們再來討論一般情況。我們把n級臺階時的跳法看成是n的函數,記爲f(n)。當n>2時,第一次跳的時候就有兩種不同的選擇:一是第一次只跳1級,此時跳法數目等於後面剩下的n-1級臺階的跳法數目,即爲f(n-1);另外一種選擇是第一次跳2級,此時跳法數目等於後面剩下的n-2級臺階的跳法數目,即爲f(n-2)。因此n級臺階時的不同跳法的總數f(n)=f(n-1)+(f-2)。
我們把上面的分析用一個公式總結如下:
/ 1 n=1
f(n)= 2 n=2
\ f(n-1) + f(n-2) n>2
原來上述問題就是我們平常所熟知的Fibonacci數列問題。可編寫代碼,如下:
- long long Fibonacci_Solution1(unsigned int n)
- {
- int result[2] = {0, 1};
- if(n < 2)
- return result[n];
- return Fibonacci_Solution1(n - 1) + Fibonacci_Solution1(n - 2);
- }
long long Fibonacci_Solution1(unsigned int n)
{
int result[2] = {0, 1};
if(n < 2)
return result[n];
return Fibonacci_Solution1(n - 1) + Fibonacci_Solution1(n - 2);
}
那麼,如果是人人筆試那道題呢?一個人上臺階可以一次上1個,2個,或者3個,豈不是可以輕而易舉的寫下如下公式:
/ 1 n=1
f(n)= 2 n=2
4 n=3 //111, 12, 21, 3
\ f(n-1)+(f-2)+f(n-3) n>3
行文至此,你可能會認爲問題已經解決了,但事實上沒有:
- 用遞歸方法計算的時間複雜度是以n的指數的方式遞增的,我們可以嘗試用遞推方法解決。具體如何操作,讀者自行思考。
- 有一種方法,能在O(logn)的時間複雜度內求解Fibonacci數列問題,你能想到麼?
- 同時,有朋友指出對於這個臺階問題只需求冪就可以了(求複數冪C++庫裏有),不用任何循環且複雜度爲O(1),如下圖所示,是否真如此?:
題目:輸入一個整數數組,調整數組中數字的順序,使得所有奇數位於數組的前半部分,
所有偶數位於數組的後半部分。要求時間複雜度爲O(n)。
分析:
- 你當然可以從頭掃描這個數組,每碰到一個偶數時,拿出這個數字,並把位於這個數字後面的所有數字往前挪動一位。挪完之後在數組的末尾有一個空位,這時把該偶數放入這個空位。由於碰到一個偶數,需要移動O(n)個數字,只是這種方法總的時間複雜度是O(n2),不符合要求,pass。
- 很簡單,維護兩個指針,一個指針指向數組的第一個數字,向後移動;一個個指針指向最後一個數字,向前移動。如果第一個指針指向的數字是偶數而第二個指針指向的數字是奇數,我們就交換這兩個數字。
- //思路,很簡答,倆指針,一首一尾
- //如果第一個指針指向的數字是偶數而第二個指針指向的數字是奇數,
- //我們就交換這兩個數字
- // 2 1 3 4 6 5 7
- // 7 1 3 4 6 5 2
- // 7 1 3 5 6 4 2
- //如果限制空間複雜度爲O(1),時間爲O(N),且奇偶數之間相對順序不變,就相當於正負數間順序調整的那道題了。
- //copyright@2010 zhedahht。
- void Reorder(int *pData, unsigned int length, bool (*func)(int));
- bool isEven(int n);
- void ReorderOddEven(int *pData, unsigned int length)
- {
- if(pData == NULL || length == 0)
- return;
- Reorder(pData, length, isEven);
- }
- void Reorder(int *pData, unsigned int length, bool (*func)(int))
- {
- if(pData == NULL || length == 0)
- return;
- int *pBegin = pData;
- int *pEnd = pData + length - 1;
- while(pBegin < pEnd)
- {
- // if *pBegin does not satisfy func, move forward
- if(!func(*pBegin)) //偶數
- {
- pBegin ++;
- continue;
- }
- // if *pEnd does not satisfy func, move backward
- if(func(*pEnd)) //奇數
- {
- pEnd --;
- continue;
- }
- // if *pBegin satisfy func while *pEnd does not,
- // swap these integers
- int temp = *pBegin;
- *pBegin = *pEnd;
- *pEnd = temp;
- }
- }
- bool isEven(int n)
- {
- return (n & 1) == 0;
- }
//思路,很簡答,倆指針,一首一尾
//如果第一個指針指向的數字是偶數而第二個指針指向的數字是奇數,
//我們就交換這兩個數字
// 2 1 3 4 6 5 7
// 7 1 3 4 6 5 2
// 7 1 3 5 6 4 2
//如果限制空間複雜度爲O(1),時間爲O(N),且奇偶數之間相對順序不變,就相當於正負數間順序調整的那道題了。
//copyright@2010 zhedahht。
void Reorder(int *pData, unsigned int length, bool (*func)(int));
bool isEven(int n);
void ReorderOddEven(int *pData, unsigned int length)
{
if(pData == NULL || length == 0)
return;
Reorder(pData, length, isEven);
}
void Reorder(int *pData, unsigned int length, bool (*func)(int))
{
if(pData == NULL || length == 0)
return;
int *pBegin = pData;
int *pEnd = pData + length - 1;
while(pBegin < pEnd)
{
// if *pBegin does not satisfy func, move forward
if(!func(*pBegin)) //偶數
{
pBegin ++;
continue;
}
// if *pEnd does not satisfy func, move backward
if(func(*pEnd)) //奇數
{
pEnd --;
continue;
}
// if *pBegin satisfy func while *pEnd does not,
// swap these integers
int temp = *pBegin;
*pBegin = *pEnd;
*pEnd = temp;
}
}
bool isEven(int n)
{
return (n & 1) == 0;
}