【python刷题】关于一个序列的入栈出栈有多少种方式相关
讲在前面
剑指offer上有这么一道题目:
题目描述
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
示例1:
输入
[1,2,3,4,5],[4,3,5,1,2]
返回值
false
解题的基本思路:
维护一个stack和cur:
- stack用于模拟进栈和出栈
- cur用于记录使用次数
1、对于4,stack:[1,2,3,4]
2、对于5,stack:[1,2,3,5]
3、对于1,stack:[1,2],此时cur == len(pushV)而且1 != 2,返回False
具体看代码:
popV = [4,3,5,2,1]
pushV = [1,2,3,4,5]
def IsPopOrder(pushV, popV):
stack = []
cur = 0
for pv in popV:
while len(stack) == 0 or pv != stack[-1] and cur < len(pushV):
stack.append(pushV[cur])
cur += 1
if cur == len(pushV) and stack[-1] != pv:
return False
else:
stack.pop()
return True
print(IsPopOrder(pushV,popV))
进一步我们想到如果我们能够依此判断一个序列是否是以一个序列的入栈和出栈序列,我们就可以计算一个序列的入栈和出栈序列有多少种?我们还需要初始序列的全排列,比如:
[1,2,3]的全排列是:[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]
于是我们有了以下代码:
popV = [4,3,5,2,1]
pushV = [1,2,3,4,5]
def permutation(array):
def helper(array, tmp):
if len(tmp) == len(array):
res.append(tmp[:])
return res
for i in array:
if i in tmp:
continue
tmp.append(i)
helper(array, tmp)
tmp.pop()
res = []
tmp = []
helper(array, tmp)
return res
def IsPopOrder(pushV, popV):
stack = []
cur = 0
for pv in popV:
while len(stack) == 0 or pv != stack[-1] and cur < len(pushV):
stack.append(pushV[cur])
cur += 1
if cur == len(pushV) and stack[-1] != pv:
return False
else:
stack.pop()
return True
print(IsPopOrder(pushV,popV))
res = (permutation(pushV))
count = 0
for array in res:
if IsPopOrder(pushV, array):
count += 1
print(count)
答案是:42
借鉴于全排列选择问题的思想,我们是否可以不用知道全皮排列就计算出总数呢?答案也是可以的,入栈和出栈就是一个选择问题。
import sys
class Solution:
def __init__(self, s):
# s为字符串或list
self.s = s
self.n = len(s)
self.result = []
def all_unstack(self, i, stack, seq):
if i == self.n:
if stack:
top = stack.pop()
seq.append(top)
self.all_unstack(i, stack, seq)
stack.append(top) # 回溯
seq.pop()
else:
self.result.append(seq[:])
else:
# 对于一个输入元素,可以入栈;可以不入,弹出栈中已有元素
# 入栈
stack.append(self.s[i])
self.all_unstack(i + 1, stack, seq)
stack.pop() # 回溯
# 出栈
if stack:
top = stack.pop()
seq.append(top)
self.all_unstack(i, stack, seq)
seq.pop() # 回溯
stack.append(top)
def print_all_sequence(self):
for each in self.result[::-1]:
print(each)
s = [1,2,3,4,5]
solution = Solution(s)
solution.all_unstack(0, [], [])
solution.print_all_sequence()
对于入栈出栈这种问题,进一步扩展:
- 饭后,姐姐洗碗,妹妹把姐姐洗过的碗一个一个地放进碗橱摞成一摞。一共有n个不同的碗,洗前也是摞成一摞的,也许因为小妹贪玩而使碗拿进碗橱不及时,姐姐则把洗过的碗摞在旁边,问:小妹摞起的碗有多少种可能的方式?
- 给定n个数,有多少种出栈序列?
- 一个有n个1和n个-1组成的字串,且前k个数的和均不小于0,那这种字串的总数为多少?
这三个问题具有相同的结构,三个问题是可以互相转化。将姐姐放碗看做入栈操作,将妹妹放碗看做出栈操作。则问题一变为问题二。将入栈操作记为1,出栈记为-1,问题2变为问题3。
这些问题的答案是一个卡特兰数。
Catalan数
卡塔兰数是组合数学中一个常在各种计数问题中出现的数列。如:
1,1,2,5,14,42,132,429,1430,4862……其递归式如下:
\(h(n)= h(0)*h(n-1)+h(1)*h(n-2) + … +h(n-2)*h(1)+ h(n-1)*h(0)\)
(其中n>=2,h(0) = h(1) = 1)
其递归式的解为h(n)=C(2n,n)/ (n+1)
用Python代码实现非常简单,仅仅一个递归就可以了:
def catalan(n):
if n==0 or n==1:
return 1
return (4*n-2)*catalan(n-1)/(n+1)
Catalan数在计算机排列组合中占有非常重要的比重,应用非常广泛,应用如下:
应用1描述:n对括号有多少种匹配方式?
应用2描述:矩阵链乘: P=a1×a2×a3×……×an,依据乘法结合律,不改变其顺序,只用括号表示成对的乘积,试问有几种括号化的方案?
应用3描述:一个栈(无穷大)的进栈序列为1,2,3,…,n,有多少个不同的出栈序列?
应用4描述:n个节点构成的二叉树,共有多少种情形?
应用5描述:一个平面凸n+2边形,若用其对角线将其划分为三角形,总共有多少种不同的划分方法?
应用5描述:有2n个人排成一行进入剧场。入场费5元。其中只有n个人有一张5元钞票,另外n人只有10元钞票,剧院无其它钞票,问有多少中方法使得只要有10元的人买票,售票处就有5元的钞票找零?
这些问题都非常相似,都是采用递归的方法!
https://blog.csdn.net/weixin_39441762/article/details/106438966
https://www.cnblogs.com/Xilian/p/3775750.html
https://blog.csdn.net/hecongqing/article/details/52833102