代码随想录算法训练营第十天 | 栈与队列part01
理论基础
如图所示:队列是先进先出,栈是先进后出。
232 用栈实现队列
因为缺乏基础,所以第一轮刷题先把代码看懂,先把意思理解。
栈是FILO,队列是FIFO,所以说如果想要用栈实现队列,那么必须要让先进的先出。
所以就需要有两个栈,第一个栈用于队列中的FI,第二个栈用于队列中的FO。
第一个栈很好写,有元素进来,那么直接添加到栈里面就可以。
第二个栈,一旦有出栈的情况,那么就需要把第一个栈中所有的元素都LO到第二个栈里,这样第二个栈的出栈顺序就和第一个栈相反,和队列相同。
了解了大致思路以后那么就可以写代码了。
首先是需要这个队列里面有2个栈,一个是stack_in,一个是stack_out。
然后写一个push方法,往stack_in栈中添加元素
再写一个pop方法,如果要是stack_in和stack_out都是empty,那么return None,因为没有元素 如果要是stack_out也就是我们的出栈有元素的话,那么就pop第一个 如果要是stack_out没有元素,但是stack_in中有元素的话,此时调用pop方法,那么就需要先把stack_in中的所有元素倒序放入stack_in中。放完以后就相当于队列中的FO了,就可以return pop
peek()方法是返回队列开头的元素,
ans = self.pop(): 这一步调用pop()方法获取队列前端的元素。pop()方法会实际从stack_out移除这个元素。self.stack_out.append(ans): 由于我们只想查看元素而不是移除它,所以我们立即将刚刚"弹出"的元素重新添加到stack_out中。return ans: 最后返回这个元素值。
class MyQueue:
def __init__(self):
"""
in主要负责push,out主要负责pop
"""
self.stack_in = []
self.stack_out = []
def push(self, x: int) -> None:
"""
有新元素进来,就往in里面push
"""
self.stack_in.append(x)
def pop(self) -> int:
"""
Removes the element from in front of queue and returns that element.
"""
if self.empty():
return None
if self.stack_out:
return self.stack_out.pop()
else:
for i in range(len(self.stack_in)):
self.stack_out.append(self.stack_in.pop())
return self.stack_out.pop()
def peek(self) -> int:
"""
Get the front element.
"""
ans = self.pop()
self.stack_out.append(ans)
return ans
def empty(self) -> bool:
"""
只要in或者out有元素,说明队列不为空
"""
return not (self.stack_in or self.stack_out)
225 用队列实现栈
from collections import deque
class MyStack:
def __init__(self):
"""
Python普通的Queue或SimpleQueue没有类似于peek的功能
也无法用索引访问,在实现top的时候较为困难。
用list可以,但是在使用pop(0)的时候时间复杂度为O(n)
因此这里使用双向队列,我们保证只执行popleft()和append(),因为deque可以用索引访问,可以实现和peek相似的功能
in - 存所有数据
out - 仅在pop的时候会用到
"""
self.queue_in = deque()
self.queue_out = deque()
def push(self, x: int) -> None:
"""
直接append即可
"""
self.queue_in.append(x)
def pop(self) -> int:
"""
1. 首先确认不空
2. 因为队列的特殊性,FIFO,所以我们只有在pop()的时候才会使用queue_out
3. 先把queue_in中的所有元素(除了最后一个),依次出列放进queue_out
4. 交换in和out,此时out里只有一个元素
5. 把out中的pop出来,即是原队列的最后一个
tip:这不能像栈实现队列一样,因为另一个queue也是FIFO,如果执行pop()它不能像
stack一样从另一个pop(),所以干脆in只用来存数据,pop()的时候两个进行交换
"""
if self.empty():
return None
for i in range(len(self.queue_in) - 1):
self.queue_out.append(self.queue_in.popleft())
self.queue_in, self.queue_out = self.queue_out, self.queue_in # 交换in和out,这也是为啥in只用来存
return self.queue_out.popleft()
def top(self) -> int:
"""
写法一:
1. 首先确认不空
2. 我们仅有in会存放数据,所以返回第一个即可(这里实际上用到了栈)
写法二:
1. 首先确认不空
2. 因为队列的特殊性,FIFO,所以我们只有在pop()的时候才会使用queue_out
3. 先把queue_in中的所有元素(除了最后一个),依次出列放进queue_out
4. 交换in和out,此时out里只有一个元素
5. 把out中的pop出来,即是原队列的最后一个,并使用temp变量暂存
6. 把temp追加到queue_in的末尾
"""
# 写法一:
# if self.empty():
# return None
# return self.queue_in[-1] # 这里实际上用到了栈,因为直接获取了queue_in的末尾元素
# 写法二:
if self.empty():
return None
for i in range(len(self.queue_in) - 1):
self.queue_out.append(self.queue_in.popleft())
self.queue_in, self.queue_out = self.queue_out, self.queue_in
temp = self.queue_out.popleft()
self.queue_in.append(temp)
return temp
def empty(self) -> bool:
"""
因为只有in存了数据,只要判断in是不是有数即可
"""
return len(self.queue_in) == 0
思路:用一个队列来实现
队列是FIFO的,而栈是FILO的。所以当栈pop也就是获取队列中最后一个元素的时候,我们就可以先将队列中的最后一个元素之前的元素全部出队,然后重新入队,这样结束之后,在进行出队,就获取到的是最后一个元素。
优化,使用一个队列实现
self.que = deque(): 创建一个新的空 deque 对象。self.que.append(x): 在 deque 的右端(末尾)添加一个元素。self.que.popleft(): 移除并返回 deque 最左端(开头)的元素。len(self.que): 返回 deque 的长度。self.que[-1]: 访问 deque 的最后一个元素。
注意:为什么不是self.que.appendleft(temp) 而是self.que.append(temp)
模拟栈行为:通过将元素移到队列末尾,我们实际上是在模拟将栈顶元素"压回"栈底,这与真实栈的 top 操作(查看但不移除栈顶元素)的效果相同。
class MyStack:
def __init__(self):
self.que = deque()
def push(self, x: int) -> None:
self.que.append(x)
def pop(self) -> int:
if self.empty():
return None
for i in range(len(self.que)-1):
self.que.append(self.que.popleft())
return self.que.popleft()
def top(self) -> int:
# 写法一:
# if self.empty():
# return None
# return self.que[-1]
# 写法二:
if self.empty():
return None
for i in range(len(self.que)-1):
self.que.append(self.que.popleft())
temp = self.que.popleft()
self.que.append(temp)
return temp
def empty(self) -> bool:
return not self.que
20 有效的括号
思路:使用栈的先进后出。
因为有三种类型的括号,但是都是由左右两种组成,所以说我们遇到左括号可以放进栈中右括号,如果遇到右括号,就可以和栈顶元素进行比较,如果相同,那么就出栈。
这是整体的思路,但是其实存在有3种情况。
1:左括号多了 (() 那就是栈中还有元素,但是字符串已经遍历完了 2:右括号多了 ()) 那就是字符串中还有右括号,但是栈已经空了 3:中间就不匹配了,走不到最后(({)) { 和 )不匹配,
具体代码实现如下:
stack = []
for i in s:
if i == '(':
stack.append(')')
elif i == '{' :
stack.append('}')
elif i == '[' :
stack.append(']')
elif stack != [] and i == stack.pop():
continue
else:
return False
if stack == []:
return True
else :
return False
下面的代码更加简洁
stack = []
for item in s:
if item == '(':
stack.append(')')
elif item == '[':
stack.append(']')
elif item == '{':
stack.append('}')
# 如果栈空了或者栈为
elif not stack or stack[-1] != item:
return False
else:
stack.pop()
return True if not stack else False
1047 删除字符串中的所有相邻重复项
感觉和括号的题目差不多。
思路:用栈来进行,如果遇到a,那么a入栈,遇到b,b入栈,再遇到b了,也就是和现在的栈顶元素一样了,那么就出栈,遇到a了和现在栈顶元素相同,那么就出栈,再把c和a入栈,发现字符串遍历完了,现在把栈里面的元素,输出即可
下面的代码依旧用到了之前的方法,先补充0,然后再创建一个新的列表,把所有的0都去掉,然后变成字符串进行输出。
s = "abbaca"
stack = [0] * len(s)
for i in s:
if i != stack[-1]:
stack.append(i)
elif i == stack[-1]:
stack.pop()
continue
list_ = []
for i in stack:
if i != 0:
list_.append(i)
print(''.join(list_))
好的代码如下
使用栈
res = list()
for item in s:
if res and res[-1] == item:
res.pop()
else:
res.append(item)
return "".join(res) # 字符串拼接
使用双指针模拟栈
# 方法二,使用双指针模拟栈,如果不让用栈可以作为备选方法。
res = list(s)
slow = fast = 0
length = len(res)
while fast < length:
# 如果一样直接换,不一样会把后面的填在slow的位置
res[slow] = res[fast]
# 如果发现和前一个一样,就退一格指针
if slow > 0 and res[slow] == res[slow - 1]:
slow -= 1
else:
slow += 1
fast += 1
return ''.join(res[0: slow])
如果对这种方法理解不了的话,可以尝试使用如下代码可视化一下过程
import React, { useState, useEffect } from 'react';
const RemoveDuplicatesVisualizer = () => {
const [input, setInput] = useState('abbaca');
const [step, setStep] = useState(0);
const [res, setRes] = useState([]);
const [slow, setSlow] = useState(0);
const [fast, setFast] = useState(0);
useEffect(() => {
setRes([...input]);
setSlow(0);
setFast(0);
setStep(0);
}, [input]);
const nextStep = () => {
if (fast < res.length) {
const newRes = [...res];
newRes[slow] = newRes[fast];
let newSlow = slow;
if (slow > 0 && newRes[slow] === newRes[slow - 1]) {
newSlow--;
} else {
newSlow++;
}
setRes(newRes);
setSlow(newSlow);
setFast(fast + 1);
setStep(step + 1);
}
};
const reset = () => {
setRes([...input]);
setSlow(0);
setFast(0);
setStep(0);
};
return (
<div className="p-4">
<div className="mb-4">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
className="border p-2 mr-2"
/>
<button onClick={reset} className="bg-blue-500 text-white p-2 rounded">Reset</button>
</div>
<div className="mb-4">
<button onClick={nextStep} className="bg-green-500 text-white p-2 rounded mr-2">Next Step</button>
<span>Step: {step}</span>
</div>
<div className="font-mono text-lg">
{res.map((char, index) => (
<span
key={index}
className={`inline-block w-8 h-8 text-center border ${
index === slow ? 'bg-red-300' : ''
} ${index === fast ? 'bg-blue-300' : ''}`}
>
{char}
</span>
))}
</div>
<div className="mt-2">
<span className="mr-4">Slow: {slow}</span>
<span>Fast: {fast}</span>
</div>
<div className="mt-2">
Result: {res.slice(0, slow).join('')}
</div>
</div>
);
};
export default RemoveDuplicatesVisualizer;