題目
設有一個數列的輸入順序爲123456,若採用棧結構,並以A和D分別表示入棧和出棧操作,試問通過進棧和出棧操作的合法序列有多少種?
解題
三種辦法,結果是132種序列。
1. 列舉法
不是直接一個個去列,而是找技巧。先列數列元素個數爲1的,再是2的,最後一直到6個,可以找到一定的規律得出結果。
2. 代碼
畢竟作爲一名軟件工程的學生,用老師的話說,“你們寫代碼的能力是要求比計算機科學的同學強的,寫代碼對於你們應該像喝水吃飯一樣簡單,這是最基礎的!”。於是我在列舉法之後考慮是否能用代碼解決這個問題。事實證明是可以的,我的想法如下:
入棧、出棧順序(A,D的排列)有兩個要求:
1. 共有12個位置,A、D各六個;
2. 任取前n個(n<=12),A的數量應該大於等於D的數量(入了才能出啊,入的不能少於出的),且n=12時,num(A)=num(D)=6
只用遍歷每一種可能即可。那麼應該如何遍歷?使得容易檢測這兩個條件,且使得時間複雜度較小(12個for循環就算了)
我的想法是利用二進制數遍歷。即遍歷0~-1這4096個十進制數,轉化爲二進制數,爲了檢驗第二個條件,我把12進制數中的0變爲-1,1不變,只用檢測前n位加起來是否>=0即可,取出每一位的方法是先除再取餘。具體代碼如下:
// main.cpp
// gcc編譯通過
#include <iostream>
#include <stdio.h>
using namespace std;
// 10的11次方已經超過int範圍
// 十進制轉換爲二進制,用的遞歸
long long transform(int num)
{
if(!num) return 0;
return num % 2 + 10 * transform(num / 2);
}
// 計算10的次方,目的是取數字的每一位
// C++自帶的pow以及pow10返回double,不符合要求,只好自己寫
long long pow10(int b)
{
long long sum = 1;
for (int i = 0; i < b; i++)
{
sum *= 10;
}
return sum;
}
int main()
{
int sum = 0;
int count = 0;
long long num2 = 0;
int tmp = 0;
bool flag = false;
// 遍歷可能性
for (int a = 4095; a >= 1; a -= 1)
{
sum = 0;
flag = true;
num2 = transform(a);
for (int b = 11; b >= 0; b--)
{
// 取每一位
tmp = (num2 / pow10(b)) % 10;
if (tmp == 0) sum += -1;
else if (tmp == 1) sum += 1;
if (sum < 0)
{
flag = false;
break;
}
}
if (flag && !sum)
{
count++;
}
}
cout << count <<endl;
getchar();
return 0;
}
3. 公式法
我認爲這個題目上面兩種辦法還是需要懂一點腦筋的,於是問其他幾個同學他們是怎麼做的,結果被告知他們都是直接上網查這個題目,發現有公式,就直接拿過來解題了((⊙o⊙)…,機智啊)。
這個棧的出棧序列是卡特蘭數的一個運用。
“
首次出空之前第一個出棧的序數k將1~n的序列分成兩個序列,其中一個是1~k-1,序列個數爲k-1,另外一個是k+1~n,序列個數是n-k。
此時,我們若把k視爲確定一個序數,那麼根據乘法原理,f(n)的問題就等價於——序列個數爲k-1的出棧序列種數乘以序列個數爲n - k的出棧序列種數,即選擇k這個序數的f(n)=f(k-1)×f(n-k)。而k可以選1到n,所以再根據加法原理,將k取不同值的序列種數相加,得到的總序列種數爲:f(n)=f(0)f(n-1)+f(1)f(n-2)+……+f(n-1)f(0)。
”
卡特蘭數的公式是:
其中n爲元素個數