栈和队列
栈
栈的抽象数据类型由以下结构和操作定义。如上所述,栈被构造为项的有序集合,其中项被添加和从末端移除的位置称为“顶部”。栈是有序的 LIFO 。栈操作如下。
Stack() 创建一个空的新栈。 它不需要参数,并返回一个空栈。
push(item)将一个新项添加到栈的顶部。它需要 item 做参数并不返回任何内容。
pop() 从栈中删除顶部项。它不需要参数并返回 item 。栈被修改。
peek() 从栈返回顶部项,但不会删除它。不需要参数。 不修改栈。
isEmpty() 测试栈是否为空。不需要参数,并返回布尔值。
size() 返回栈中的 item 数量。不需要参数,并返回一个整数。
基本编程
1. 用数组实现一个顺序栈
参考:
https://facert.gitbooks.io/python-data-structure-cn/3.基本数据结构/3.5.Python实现栈/
class Stack(object):
def __init__(self):
self.item=[]
def push(self,item):
self.item.append(item)
def pop(self):
return self.item.pop()
def peek(self):
return self.item[len(self.item)-1]
def isEmpty(self):
return self.item==[]
def size(self):
return len(self.item)
2. 用链表实现一个链式栈
参考:
https://blog.csdn.net/zhisuihen6347/article/details/84496891
https://blog.csdn.net/weixin_40520563/article/details/82825526
class Node():
def __init__(self,item):
self.item=item
self.next=None
class Stack():
def __init__(self):
self.head=None
self.N=0
def push(self,item):
oldhead=self.head
self.head=Node(item)
self.head.next=oldhead
self.N+=1
def pop(self):
if self.N==0:
print('Stackunderflow')
else:
item=self.head.item
self.head=self.head.next
return item
def peek():
if self.N==0:
print('Stackunderflow')
else:
item=self.head.item
return item
def isEmpty():
return self.N==0
def size():
return self.N
3. 编程模拟实现一个浏览器的前进、后退功能
参考:https://www.jianshu.com/p/0dace6c0fe59
- 需要建立两个线性栈,一个主栈用来保存当前的页面和之前的几个页面,一个副栈用来保存当前页面之后的几个页面。
- 当进行后退操作时,副栈获取主栈最上面的元素,主栈删除这个元素,从而后退到前一个页面。
- 当进行前进操作时,主栈获取副栈最上面的元素,副栈删除这个元素,从而前进到下一个页面。
- 当新的页面被创建,显然新页面前面不存在其他页面,这是我们要清空副栈。
- 当主栈中只有一个元素时,显然浏览器已经后退到第一个页面,不能再后退了;当副栈没有元素时,显然浏览器已经前进到最后一个页面了,不能再前进了。
import curses
from curses import wrapper
from datetime import datetime
stdscr = curses.initscr()
count = 1
class StackBrowser(object):
def __init__(self, length=200):
self.item = []
self.length = length
def push(self, value):
if self.is_full():
return None
else:
self.item.append(value)
def pop(self):
if self.is_empty():
return None
self.item.pop()
def top(self):
if self.is_empty():
return None
return self.item[-1]
def is_empty(self):
return len(self.item) == 0
def is_full(self):
if len(self.item) >= self.length:
return True
def size(self):
return len(self.item)
def show_stack(self):
print(self.item)
def print_board(stdscr):
# stdscr.clear()
# 清除屏幕
for i in range(4):
stdscr.addstr('-' * 22 + '\n')
for j in range(4):
stdscr.addstr('|')
if board[i, j]:
stdscr.addstr('{:^4d}'.format(board[i, j]))
else:
stdscr.addstr(' '.format())
stdscr.addstr('|')
stdscr.addstr('\n')
stdscr.addstr('-' * 22 + '\n')
def print_browser(stdscr, order, stack, temp_stack):
global count
# datetime.now() 获取当前时间和日期
# strftime 将日期格式化输出 年-月-日 时:分:秒
now = '网页创建时间:{}'.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S \n'))
stdscr.clear()
# 退出程序
if order == ord('q'):
exit()
# 新建页面
elif order == ord('n'):
count += 1
stack.push(now)
while not temp_stack.is_empty():
temp_stack.pop()
# 后退功能
elif order == ord('b'):
length = stack.size()
if length > 1:
temp_stack.push(stack.top())
stack.pop()
# 前进功能
elif order == ord('f'):
length = temp_stack.size()
if length >= 1:
stack.push(temp_stack.top())
temp_stack.pop()
# 打印功能
if stack.top():
stack_size = stack.size()
sum_size = stack.size() + temp_stack.size()
stdscr.addstr(stack.top())
stdscr.addstr('共创建了 {} 个页面 \n'.format(count))
stdscr.addstr('当前页面:第 {} 页 \n'.format(stack_size))
stdscr.addstr('共 {} 页 \n'.format(sum_size))
def main(stdscr):
global count
# 不显示输入
curses.noecho()
# 创建 stack 类
# stack 为主栈
# temp_stack 为副栈
stack = StackBrowser()
temp_stack = StackBrowser()
# 获取当前时间
now = '网页创建时间:{}'.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S \n'))
# 栈保存网页创建时间
stack.push(now)
stack_size = stack.size()
sum_size = stack.size() + temp_stack.size()
stdscr.addstr(stack.top())
stdscr.addstr('共创建了 {} 个页面 \n'.format(count))
stdscr.addstr('当前页面:第 {} 页 \n'.format(stack_size))
stdscr.addstr('共 {} 页 \n'.format(sum_size))
while 1:
order = stdscr.getch()
print_browser(stdscr=stdscr, order=order, stack=stack, temp_stack=temp_stack)
if __name__ == '__main__':
wrapper(main)
练习
1. Valid Parentheses(有效的括号)
英文版:https://leetcode.com/problems/valid-parentheses/
中文版:https://leetcode-cn.com/problems/valid-parentheses/
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
示例 1:
输入: “()”
输出: true
示例 2:
输入: “()[]{}”
输出: true
示例 3:
输入: “(]”
输出: false
示例 4:
输入: “([)]”
输出: false
示例 5:
输入: “{[]}”
输出: true
from pythonds.basic.stack import Stack
def parChecker(symbolString):
s = Stack()
balanced = True
index = 0
while index < len(symbolString) and balanced:
symbol = symbolString[index]
if symbol in "([{":
s.push(symbol)
else:
if s.isEmpty():
balanced = False
else:
top = s.pop()
if not matches(top,symbol):
balanced = False
index = index + 1
if balanced and s.isEmpty():
return True
else:
return False
def matches(open,close):
opens = "([{"
closers = ")]}"
return opens.index(open) == closers.index(close)
2. Evaluate Reverse Polish Notatio(逆波兰表达式求值)
英文版:https://leetcode.com/problems/evaluate-reverse-polish-notation/
中文版:https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/
根据逆波兰表示法,求表达式的值。
有效的运算符包括 +, -, *, / 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
说明:
整数除法只保留整数部分。
给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。
示例 1:
输入: [“2”, “1”, “+”, “3”, “"]
输出: 9
解释: ((2 + 1) * 3) = 9
示例 2:
输入: [“4”, “13”, “5”, “/”, “+”]
输出: 6
解释: (4 + (13 / 5)) = 6
示例 3:
输入: [“10”, “6”, “9”, “3”, “+”, “-11”, "”, “/”, “*”, “17”, “+”, “5”, “+”]
输出: 22
解释:
((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22
from pythonds.basic.stack import Stack
def postfixEval(postfixExpr):
operandStack = Stack()
tokenList = postfixExpr.split()
for token in tokenList:
if token in "0123456789":
operandStack.push(int(token))
else:
operand2 = operandStack.pop()
operand1 = operandStack.pop()
result = doMath(token,operand1,operand2)
operandStack.push(result)
return operandStack.pop()
def doMath(op, op1, op2):
if op == "*":
return op1 * op2
elif op == "/":
return op1 / op2
elif op == "+":
return op1 + op2
else:
return op1 - op2
参考:
队列
- 队列是项的有序结合,其中添加新项的一端称为队尾,移除项的一端称为队首。当一个元素从队尾进入队列时,一直向队首移动,直到它成为下一个需要移除的元素为止。
- 最近添加的元素必须在队尾等待。集合中存活时间最长的元素在队首,这种排序成为 FIFO,先进先出,也被成为先到先得。
基本操作
队列抽象数据类型由以下结构和操作定义。如上所述,队列被构造为在队尾添加项的有序集合,并且从队首移除。队列保持 FIFO 排序属性。 队列操作如下。Queue() 创建一个空的新队列。 它不需要参数,并返回一个空队列。
enqueue(item) 将新项添加到队尾。 它需要 item 作为参数,并不返回任何内容。
dequeue() 从队首移除项。它不需要参数并返回 item。 队列被修改。
isEmpty() 查看队列是否为空。它不需要参数,并返回布尔值。
size() 返回队列中的项数。它不需要参数,并返回一个整数。
1. 用数组实现一个顺序队列
假定队尾在列表中的位置为 0。这允许我们使用列表上的插入函数向队尾添加新元素。弹出操作可用于删除队首的元素(列表的最后一个元素)。这也意味着入队为 O(n),出队为 O(1)。
class Queue():
#前进后出
def __init__(self):
self.item=[]
def isEmpty(self):
return self.item==[]
def enqueue(self,item):
self.item.insert(0,item)
def dequeue():
return self.item.pop()
def size():
return len(self.item)
2. 用链表实现一个链式队列
注:链表的头部容易被删掉,所以在链表头部添加元素(队尾rear),在尾部删除元素(队首front)
class Empty(Exception):
def __init__(self, m):
super().__init__(self)
self.message = m
def __str__(self):
return self.message
class Node():
def __init__(self,item):
self.item=item
self.next=None
class Queue2():
def __init__(self):
self.rear=None
self.front=None
self.N=0
def isEmpty(self):
return self.N==0
def enqueue(self,item):
a=Node(item)
if self.N==0:
self.front=a
self.rear=a
else:
self.rear.next=a
self.rear=a
self.N+=1
def dequeue(self):
if self.N==0:
raise Empty('Queue is empty')
item=self.front.item
self.front=self.front.next
self.N-=1
return item
def size(self):
return self.N
3. 实现一个循环队列
参考:https://blog.csdn.net/u012626619/article/details/80658397
- 循环队列包括两个指针, front 指针指向队头元素, rear 指针指向队尾元素的下一个位置。
- 队列为空的判断条件是:
front == rear
队列满的判断条件是:
(rear+1)%maxsize == front
队列长度的计算公式:
(rear-front+maxsize)%maxsize
class SqQueue(object):
def __init__(self, maxsize):
self.queue = [None] * maxsize
self.maxsize = maxsize
self.front = 0
self.rear = 0
# 返回当前队列的长度
def QueueLength(self):
return (self.rear - self.front + self.maxsize) % self.maxsize
# 如果队列未满,则在队尾插入元素,时间复杂度O(1)
def EnQueue(self, data):
if (self.rear + 1) % self.maxsize == self.front:
print("The queue is full!")
else:
self.queue[self.rear] = data
# self.queue.insert(self.rear,data)
self.rear = (self.rear + 1) % self.maxsize
# 如果队列不为空,则删除队头的元素,时间复杂度O(1)
def DeQueue(self):
if self.rear == self.front:
print("The queue is empty!")
else:
data = self.queue[self.front]
self.queue[self.front] = None
self.front = (self.front + 1) % self.maxsize
return data
# 输出队列中的元素
def ShowQueue(self):
for i in range(self.maxsize):
print(self.queue[i],end=',')
print(' ')
练习:
1. Design Circular Deque(设计一个双端队列)
英文版:https://leetcode.com/problems/design-circular-deque/
中文版:https://leetcode-cn.com/problems/design-circular-deque/
你的实现需要支持以下操作:
MyCircularDeque(k):构造函数,双端队列的大小为k。
insertFront():将一个元素添加到双端队列头部。 如果操作成功返回 true。
insertLast():将一个元素添加到双端队列尾部。如果操作成功返回 true。
deleteFront():从双端队列头部删除一个元素。 如果操作成功返回 true。
deleteLast():从双端队列尾部删除一个元素。如果操作成功返回 true。
getFront():从双端队列头部获得一个元素。如果双端队列为空,返回 -1。
getRear():获得双端队列的最后一个元素。 如果双端队列为空,返回 -1。
isEmpty():检查双端队列是否为空。
isFull():检查双端队列是否满了。
示例:
MyCircularDeque circularDeque = new MycircularDeque(3); // 设置容量大小为3
circularDeque.insertLast(1); // 返回 true
circularDeque.insertLast(2); // 返回 true
circularDeque.insertFront(3); // 返回 true
circularDeque.insertFront(4); // 已经满了,返回 false
circularDeque.getRear(); // 返回 2
circularDeque.isFull(); // 返回 true
circularDeque.deleteLast(); // 返回 true
circularDeque.insertFront(4); // 返回 true
circularDeque.getFront(); // 返回 4
提示:
所有值的范围为 [1, 1000]
操作次数的范围为 [1, 1000]
请不要使用内置的双端队列库。
class MyCircularDeque:
def __init__(self, k: int):
"""
Initialize your data structure here. Set the size of the deque to be k.
"""
self.head, self.tail = -1, -1
self.vec = [None for _ in range(k)]
def insertFront(self, value: int) -> bool:
"""
Adds an item at the front of Deque. Return true if the operation is successful.
"""
if self.isFull(): return False
self.head -= 1
if self.head < 0:
self.head = len(self.vec)-1
self.vec[self.head] = value
return True
def insertLast(self, value: int) -> bool:
"""
Adds an item at the rear of Deque. Return true if the operation is successful.
"""
if self.isFull():
return False
self.tail += 1
if self.tail == len(self.vec):
self.tail = 0
self.vec[self.tail] = value
return True
def deleteFront(self) -> bool:
"""
Deletes an item from the front of Deque. Return true if the operation is successful.
"""
if self.isEmpty():
return False
self.vec[self.head] = None
self.head += 1
return True
def deleteLast(self) -> bool:
"""
Deletes an item from the rear of Deque. Return true if the operation is successful.
"""
if self.isEmpty(): return False
self.vec[self.tail] = None
self.tail -= 1
return True
def getFront(self) -> int:
"""
Get the front item from the deque.
"""
print(self.head, len(self.vec))
return self.vec[self.head]
def getRear(self) -> int:
"""
Get the last item from the deque.
"""
return self.vec[self.tail]
def isEmpty(self) -> bool:
"""
Checks whether the circular deque is empty or not.
"""
for x in self.vec:
if x != None:
return False
return True
def isFull(self) -> bool:
"""
Checks whether the circular deque is full or not.
"""
for x in self.vec:
if x == None:
return False
return True
# Your MyCircularDeque object will be instantiated and called as such:
# obj = MyCircularDeque(k)
# param_1 = obj.insertFront(value)
# param_2 = obj.insertLast(value)
# param_3 = obj.deleteFront()
# param_4 = obj.deleteLast()
# param_5 = obj.getFront()
# param_6 = obj.getRear()
# param_7 = obj.isEmpty()
# param_8 = obj.isFull()
2. Sliding Window Maximum(滑动窗口最大值)
英文版:https://leetcode.com/problems/sliding-window-maximum/
中文版:https://leetcode-cn.com/problems/sliding-window-maximum/
给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口 k 内的数字。滑动窗口每次只向右移动一位。返回滑动窗口最大值。
示例:
输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7]
解释:
滑动窗口的位置 最大值
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7
注意:
你可以假设 k 总是有效的,1 ≤ k ≤ 输入数组的大小,且输入数组不为空。
#滑动窗口最大值,双端队列
class Solution(object):
def maxSlidingWindow(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: List[int]
"""
if not nums or k == 0:
return []
if k == 1:
return nums
#队列里保存的是索引
queue = [0]
res = []
for i in range(1, len(nums)):
#队列的第一个索引始终为最大值索引,(当前索引-队列第一个索引)大于滑动窗口长度时,删除第一个索引
if i-queue[0] >= k:
queue.pop(0)
#保证当前最大的值的索引是第一个
while queue and nums[queue[-1]] < nums[i]:
queue.pop(-1)
queue.append(i)
if i >= k-1:
res.append(nums[queue[0]])
return res
递归
编程
1.编程实现斐波那契数列求值 f(n)=f(n-1)+f(n-2)
参考:https://www.jianshu.com/p/fb970cf597b3
def fibonacci(n):
if n==1 or n==2:
return 1
return fibonacci(n-1)+fibonacci(n-2)
2.编程实现求阶乘 n!
https://www.cnblogs.com/ncuhwxiong/p/7060689.html
def factorial(n):
if n==1 or n==0:
return 1
else:
return n*factorial(n-1)
number = int(input("请输入一个正整数:"))
result = factorial(number)
print("%d 的阶乘是 %d" %(number,result))
3. 编程实现一组数据集合的全排列
参考:https://blog.csdn.net/qq_42015869/article/details/79996227
排列:从n个元素中任取m个元素,并按照一定的顺序进行排列,称为排列;
全排列:当n==m时,称为全排列;
比如:集合{ 1,2,3}的全排列为:
{ 1 2 3}
{ 1 3 2 }
{ 2 1 3 }
{ 2 3 1 }
{ 3 2 1 }
{ 3 1 2 }
递归全排列:
1、列表只有一个元素[a],它的全排列只有a。
2、列表有两个元素[a, b],它的全排列为[a, b], [b, a]:
{ 将第一个元素a固定,对b进行全排列得到[a, b]。
将第一个元素与第二个元素交换得到[b, a]。
将b固定,对a进行全排列,得到[b, a] }
3、列表有三个元素[a, b, c]
{ 将a固定,对bc进行全排列{ 将b固定,对c全排列[abc]。交换bc,将c固定对b进行全排列[acb] }
交换ab,[b, a, c] 对ac进行全排列{ … }
… …}
4、列表有n个元素,将第一个元素固定,对剩下n - 1个元素进行全排列。
将第一个元素依此与其他元素交换,对每次交换后剩下的n-1个元素进行全排列。
5、对剩下的n - 1个元素全排列,同上,固定后对n - 2排列。
6、直到数组数量为1,全排列就是它自己,完成一次排列。
def perm(data, begin, end):
if begin == end: # 递归结束条件,当交换到最后一个元素的时候不需要交换,1的全排列还是1。
print(data) # 打印一次排列完成后的数组。
else:
j = begin
for i in range(begin, end): # 从begin到end全排列。
data[i], data[j] = data[j], data[i]
perm(data, begin + 1, end)
data[i], data[j] = data[j], data[i] # 递归完成后,交换回原来的位置。
练习:
Climbing Stairs(爬楼梯)
英文版:https://leetcode.com/problems/climbing-stairs/
中文版:https://leetcode-cn.com/problems/climbing-stairs/
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1:
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1.1 阶 + 1 阶
2.2 阶
示例 2:
输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1.1 阶 + 1 阶 + 1 阶
2.1 阶 + 2 阶
3.2 阶 + 1 阶
class Solution(object):
#借助阶乘
def factorial(self,n):
if n==1 or n==0:
return 1
else:
return n*self.factorial(n-1)
def climbStairs(self, n):
"""
:type n: int
:rtype: int
"""
num=n
num_1=n
num_2=0
methord=0
while (n-2*num_2)>=0:
methord=methord+self.factorial(num)/(self.factorial(num_1)*self.factorial(num_2))
num_2+=1
num_1=num_1-2
num=num_1+num_2
return methord
class Solution:
#动态规划,用数组记录每个台阶的所有走法个数,时间复杂度降为 O(n)
def climbStairs(self, n):
# 递归实现,时间太长,舍去
# 使用动态规划
if n<4:
return n
else:
l = [1,2,3]
for i in range(3,n):
l.append(l[i-1] + l[i-2])
return l[i]
class Solution:
#动态规划,由于只需要返回最后一步的所有走法个数,不需要数组记录过程,利用Python的同时赋值特性,只需两个变量就行,空间复杂度降为 O(1)
def climbStairs(self, n):
one = two = 1
for _ in range(1, n):
one, two = one + two, one
return one
#递归,显然是个斐波那契数列,时间复杂度 O(2^n)很高,这样没法通过LeetCode,参考 @wikizero 的解法可以加个LRU缓存
class Solution:
# @lru_cache(10**8)
def climbStairs(self, n: int) -> int:
if n <= 2:
return n
return self.climbStairs(n - 1) + self.climbStairs(n - 2)