要介绍栈和队列的算法,我们就得先知道:啥是栈和队列啊?
栈和队列的定义
栈 :它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底
队列: 队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。
OK , 介绍完基本知识 , 来实战一下呗
包含min函数的栈(剑指offer 21题)
题目:定义栈的数据结构 , 请在该类型中实现一个能够得到栈的最小元素的min函数。在该栈中, 调用 min , push 及 pop 的时间复杂度都是 O(1)
一看到这个问题 , 一眼看上去 , 最难就是怎么在 O(1)的时间复杂度内 实现 取最小值的操作呢 ?
我们想到在栈里添加一个成员变量存放最小的元素, 每次压入一个新元素进栈的时候, 如果该元素比当前最小的元素还小 , 则更新最下元素。当我们想到这样的思路的时候,仔细想想就会发现 , 如果当前最小的元素被弹出栈了 , 这样我怎么知道下一个最小的元素是什么呢?
分析到这里我们发现仅仅添加一个成员变量存放最小是不够的, 也就是说当最小元素被弹出栈的时候 , 我们希望能够得到次小元素。因此在压入这个最小元素之前 , 我们要把次小元素保存起来。
OK , 我们这里引入辅助栈, 这里我们为了好理解
举个栗子 两个栈
stack = []
help = []
从栈中 入栈一个元素 , stack.push(1) => [1]
因为只有一个元素 , 最小值就是 那个元素 , 所以
help.push(1) => [1] , 再入一个元素 , stack.push(-1) => [1,-1] , 这时我们发现 , 我刚刚入的数 -1 , 小于 help 的栈顶元素 , 我们也入 help.push(-1) => [1- ,-1],总结起来就是这样的
stack进行push , 如果发现要存的数字小于 help的栈顶元素 , 就存入help , 否则只存入 stack
这题虽然不难,但还挺不好描述的(累死我了)。
OK, show me code
js版的
class MinStack {
constructor () {
this.stack = [] ;
// 辅助栈
this.help = [] ;
}
//入栈
push (element) {
// 如果辅助栈为空 或者 加入元素小于辅助栈顶元素 , 则辅助栈进行入栈操作
if(!this.help.length || element <= this.help[this.help.length -1]) {
this.help.push(element)
}
this.stack.push(element)
}
// 出栈
pop () {
if(this.stack.pop() === this.help[this.help.length - 1]) {
this.help.pop()
}
}
// 返回最小值
getMin() {
//返回辅助栈顶元素
return this.help[this.help.length - 1]
}
}
再来看看python版的
python版的
class MinStack(object):
def __init__(self , *args):
'''
initialize your data structure here
'''
self.stack = [] # 存放所有元素
self.minStack = [] #存放每一次压入数据时 , 栈中的最小值
def push(self ,x):
'''
:type x :int
:rtype : void
'''
self.stack.append(x)
if not self.minStack or self.minStack[-1] >= x:
self.minStack.append(x)
def pop(self):
'''
rtype: void
'''
if self.minStack[-1] == self.stack[-1]:
del self.minStack[-1]
self.stack.pop()
def top(self):
'''
:rtype:int
'''
return self.stack[-1]
def getMin(self):
'''
:rtype: int
'''
return self.minStack[-1]
OK,咋们继续下一题
用两个栈实现队列(剑指offer 第7题)
题目:用两个栈实现一个队列。队列的声明如下:请实现它的两个函数
appendTail 在队列尾部插入节点
deleteHead 在队头删除节点
思路:首先,我知道队列是先进先出 , 而栈是先进后出,为了方便我们再举个栗子:
我们有栈1 ,栈2
stack1 = []
stack2 = []
当我打算入队时 , 我们向 stack1 中,添加元素 , stack1.push(1) => [1] , 继续我们再入队 , stack1.push(3) => [1 ,3] => [1,3,-2] 。最终stack1 就会变成这样 [1,3,-2]。重点来了,
这时候我们打算出队 , 按照队列的特性 , 先进先出 , 所以 第一个出去的那个数 , 一定是 我最先入队的 1 ,
但是 如果我们对 stack1 操作, 只能 pop 把 -2 弹出来 , 明显不是想要的 。这时我借助 stack2
**我们先把 res = stack1.pop() ,再依次把 res push 到 stack2 ,最终stack2 => [-2 , 3, 1]**这时候, 我们只要 把 stack2的栈顶元素 弹出 , 便是 一开始先进队列 的 1
show me code
js版的
class Queue {
constructor () {
this.stack1 = [] // 栈1
this.stack2 = [] // 栈2
}
// 入队
appendTail (element) {
this.stack1.push(element)
}
//出队
deleteHead () {
while(this.stack1.length) {
this.stack2.push(this.stack1.pop())
}
if(!this.stack2.length) {
throw new Error('queue is empty')
}
return this.stack2.pop()
}
}
了解一下python版
class Solution(object) :
def __init__(self):
'''
initialize your data structure here
'''
self.stack1 = [] #入队栈
self.stack2 = [] #出队栈
def appendTail(self,x):
'''
:type x : int
:rtype : void
'''
self.stack1.append(x)
def deleteHead(self):
'''
rtype : int
'''
while(len(self.stack1)):
self.stack2.append(self.stack1.pop())
if(len(self.stack2) == 0):
print('queue is empty!')
return self.stack2.pop()
我们再对上面的题目进行变形一下
用两队列实现一个栈(剑指offer 课后练习)
题目: 我们通过一系列栈的压入和弹出操作来分析来两个队列模拟一个栈的过程
其实,如果有上面的经验 ,这题还是相当简单的。好的,我继续来
举个栗子:
queue1 = [] 压入队列
queue2 = [] 弹出队列
这时候我们 向 栈 中压入 3 个 元素 , 就向 queue1 中 push 3 个元素 queue1 => [1,3 -2] , 先入1 再 3 再就 -2 。 OK , 这时候我们打算弹出栈 , 我们理应 把 -2 弹出来 , 可 queue1 只有 从队头的删除操作 。这是我们用到 queue2 。首先我们得记住我们的要求:把 -2弹出来。
queue1 = [1 ,3 ,-2] 出队到 queue2
queue2 => [1 ,3] , 当发现 queue1 的队头元素是最后一个元素 -2 我们 , 我直接出队 , 并不 入队到 queue2 , 这样我们就把 -2 从中删除了
show me code
js版的
class Stack {
constructor () {
this.queue1 = [] ; // 队列1
this.queue2 = [] ; // 队列2
}
//入栈
push(element) {
// 哪个队列不为空 , push 到哪个队列
if(this.queue2.length) {
this.queue2.push(element)
} else {
this.queue1.push(element)
}
}
//出栈
pop () {
// 如果栈为空 , 则报错
if(!this.queue1.length && !this.queue2.length) {
throw new Error('stack is empty')
}
// 从非空队列 依次转移到 空队列 , 直到非空队列只剩下一个元素 , 删除最后一个元素
// queue2 为空
if(!this.queue2.length) {
while(this.queue1.length !== 1) {
this.queue2.push(this.queue1.shift())
}
return this.queue1.shift()
}
// queue1 为空
else if(!this.queue1.length) {
while(this.queue2.length !== 1) {
this.queue1.push(this.queue2.shift())
}
return this.queue2.shift()
}
}
}
python版的
class Solution:
def __init__(self):
'''
initialize your data structure here
'''
self.queue1 = [] # 压入队列
self.queue2 = [] # 弹出队列
def push(self,x):
'''
:type x : int
:rtype : void
'''
# 哪个队列不为空 , 就 push到哪个队列
if(len(self.queue2)):
self.queue2.append(x)
else:
self.queue1.append(x)
def pop(self):
'''
:rtype : void
'''
#如果栈为 空 , 则报错
if(not len(self.queue1) and not len(self.queue2)):
print('stack is empty')
# 从非空队列 依次转移到 空队列 , 直到非空队列只剩下一个元素
# queue2 为空
if(not len(self.queue2)):
while(len(self.queue1) != 1):
self.queue2.append(self.queue1.pop(0))
return self.queue1.pop(0)
# queue1 为空
elif (not len(self.queue1)):
while(len(self.queue2) != 1):
self.queue1.append(self.queue2.pop(0))
return self.queue2.pop(0)
滑动窗口最大值(有点难度)
最后, 我们来一个比较有难度的
说实话,个人感觉这题确实有难度 , 我想了比较久的时间
首先,我们来了解一下什么是滑动窗口?咋们来复习一下计算机网络呗,嘿嘿
滑动窗口的定义
滑动窗口(Sliding window) 是一种流量控制技术。在早期的网络通信中,通信双方不会考虑网络的拥塞情况直接发送数据。由于大家不知道网络拥塞状况,同时发送数据,导致中间节点阻塞包,谁也发不了数据,所以就有了滑动窗口机制来解决此问题。滑动窗口协议是用来改善吞吐量的一种技术,即容许发送方在接收任何应答之前传送附加的包。接收方告诉发送方在某一时刻能送多少包(称窗口大小)
好吧,我承认滑动窗口算法和 滑动窗口的定义 关系不是很大, 尴尬。咋们回归题目
题目
给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。