1. 程式人生 > 其它 >關於出棧序列的解法總結及卡特蘭數的學習(C語言)

關於出棧序列的解法總結及卡特蘭數的學習(C語言)

出棧次序

一個棧(無窮大)的進棧序列為1,2,3,…,n,有多少個不同的出棧序列?

解法1——遞迴/記憶化搜尋

  1. 考慮用一個二維陣列f[i][j]模擬當前情況:i——進棧序列中還有i個待排的數,j——棧中有j個數,f[i][j]的表示當前i,j情況下有幾種輸出方案。
  2. 首先如果f[i][j]有值,直接呼叫即可(記憶化搜尋,節省時間);
  3. 如果i=0,即序列全部入棧,只有一種輸出方法,所以返回1;
  4. 考慮一般情況,有兩種輸出方案,先進一個再出,即加上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——遞推

  1. 首先重設一下f[i][j],f的含義不變,i為入棧數,j為出棧數;
  2. 我們知道f[0][j]=1,因為序列全部入棧,出棧次序是唯一的。
  3. 然後我們來考慮一下遞迴關係,如何得到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]。
  4. 但涉及到出棧必須考慮棧空的情況,什麼時候棧空?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) * (4
n-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

所以要對這類題目有一定敏感性

有關題目練習可以去:


球迷購票問題
雞蛋餅