關於出棧序列的解法總結及卡特蘭數的學習(C語言)
阿新 • • 發佈:2022-03-13
出棧次序
一個棧(無窮大)的進棧序列為1,2,3,…,n,有多少個不同的出棧序列?
解法1——遞迴/記憶化搜尋
- 考慮用一個二維陣列f[i][j]模擬當前情況:i——進棧序列中還有i個待排的數,j——棧中有j個數,f[i][j]的值表示當前i,j情況下有幾種輸出方案。
- 首先如果f[i][j]有值,直接呼叫即可(記憶化搜尋,節省時間);
- 如果i=0,即序列全部入棧,只有一種輸出方法,所以返回1;
- 考慮一般情況,有兩種輸出方案,先進一個再出,即加上f[i-1][j+1],(棧不空時,j>0,如果棧空只有第一種輸出方案可行)直接出,即加上f[i][j-1]。
程式碼如下
long long f[MAX][MAX]; long long dfs(int i,int j) { if(f[i][j]) return f[i][j]; if (i == 0) return 1; if(j>0) f[i][j] += dfs(i, j - 1); f[i][j] += dfs(i - 1, j + 1); return f[i][j]; } int main() { int n; scanf("%d", &n); printf("%lld", dfs(n, 0)); return 0; }
解法2——遞推
- 首先重設一下f[i][j],f的含義不變,i為入棧數,j為出棧數;
- 我們知道f[0][j]=1,因為序列全部入棧,出棧次序是唯一的。
- 然後我們來考慮一下遞迴關係,如何得到f[i][j]?即怎麼得到i個數入棧,j個數出棧的情況,只需要有i-1個數入棧,出棧j個,此時再入棧一個就是f[i][j];同理,可以是i個數入棧,j-1個出棧。所以我們得到遞迴關係f[i][j]=f[i-1][j]+f[i][j-1]。
- 但涉及到出棧必須考慮棧空的情況,什麼時候棧空?i=j時,就只有f[i][j]=f[i-1][j]。
遞迴做法
long long f[MAX_N][MAX_N]; int main() { int n; scanf("%d",&n); for(int i=0;i<=n;i++) { f[0][i]=1; } for(int i=1;i<=n;i++) { for(int j=i;j<=n;j++) { if(i==j)f[i][j]=f[i-1][j]; else f[i][j]=f[i][j-1]+f[i-1][j]; } } printf("%lld",f[n][n]); return 0; }
解法3——卡特蘭數
關於卡特蘭數列的詳細解釋移步百度百科
- 我們可以直接用的是它的四個遞推公式
設h(n)為catalan數的第n項,令h(0)=1,h(1)=1,catalan數滿足遞推式 :
1)h(n)= h(0)h(n-1)+h(1)h(n-2) + ... + h(n-1)h(0) (n≥2)
2)h(n)=h(n-1) * (4n-2)/(n+1)
3)h(n)=C(2n,n)/(n+1) (n=0,1,2,...)
4)h(n)=C(2n,n) - C(2n,n-1) (n=0,1,2,...)
程式碼都有遞推關係了程式碼不是有手就行
//給出公式4作為參照 long long c[MAX][MAX]; int main(){ int n; scanf("%d",&n); for(int i=1;i<=2*n;i++) { c[i][0]=c[i][i]=1; for(int j=1;j<i;j++) { c[i][j]=c[i-1][j]+c[i-1][j-1]; } } printf("%lld",c[2*n][n]-c[2*n][n-1]); return 0; }
關鍵是分析為什麼可以用卡特蘭
- 原理分析:
- 建立陣列f。f[i]表示i個數的全部可能性。
f[0] = 1, f[1] = 1; //當然只有一個
設 x 為當前出棧序列的最後一個,則x有n種取值
由於x是最後一個出棧的,所以可以將已經出棧的數分成兩部分
1.比x小
2.比x大
比x小的數有x-1個,所以這些數的全部出棧可能為f[x-1]
比x大的數有n-x個,所以這些數的全部出棧可能為f[n-x]
這兩部分互相獨立,所以根據乘法原理,一個x的取值能夠得到的所有可能性為f[x-1] * f[n-x]
另外,由於x有n個取值,所以
ans = f[0]f[n-1] + f[1]f[n-2] + ... + f[n-1]*f[0]; - 或者這樣分析:
- 我們把進棧設為狀態‘1’,出棧設為狀態‘0’。n個數的所有狀態對應n個1和n個0組成的2n位二進位制數。
那麼合法序列就是總序列-非法序列,總序列(由n次出棧n次入棧操作構成的序列數)為C(2n,n)。
非法序列:由左而右掃描時,必然在某一奇數位2m+1位上首先出現m+1個0的累計數和m個1的累計數,此後的2(n-m)-1位上有n-m個 1和n-m-1個0。如若把後面這2(n-m)-1位上的0和1互換,使之成為n-m個0和n-m-1個1,結果得1個由n+1個0和n-1個1組成的2n位數,即一個不合要求的數對應於一個由n+1個0和n-1個1組成的排列。反過來,任何一個由n+1個0和n-1個1組成的2n位二進位制數,由於0的個數多2個,2n為偶數,故必在某一個奇數位上出現0的累計數超過1的累計數。同樣在後面部分0和1互換,使之成為由n個0和n個1組成的2n位數,即n+1個0和n-1個1組成的2n位數必對應一個不符合要求的數。
這就證明了不合要求的2n位數與n+1個0,n-1個1組成的排列一一對應。,為C(2n,n+1)
所以有輸出序列的總數目=c(2n,n)-c(2n,n-1)=c(2n,n)/(n+1)=h(n)。
我覺得這類題目可以總結為由特定順序(二者匹配)的兩種狀態組成的排序數類問題
類似題目有
括號序列 :n 對括號,則有多少種 “括號匹配” 的括號序列
——左括號看作1,右括號看作0,和上題一樣
二叉樹n + 1 個葉子節點能夠構成多少種形狀不同的(國際)滿二叉樹
(國際)滿二叉樹定義:如果一棵二叉樹的結點要麼是葉子結點,要麼它有兩個子結點,這樣的樹就是滿二叉樹。
——形成滿二叉樹需要先向左擴充套件,再向右擴充套件,左右匹配,所以向左看作1,向右看作0,n+1個葉子結點有2n次擴充套件,還原為出入棧
買票找零:有2n個人排成一行進入劇場。入場費5元。其中只有n個人有一張5元鈔票,另外n人只有10元鈔票,劇院無其它鈔票,問有多少種方法使得只要有10元的人買票,售票處就有5元的鈔票找零
——拿5元的看作1,10元的看作0
所以要對這類題目有一定敏感性