代码随想录算法训练营第十天 | 栈与队列part01

92 阅读7分钟

代码随想录算法训练营第十天 | 栈与队列part01

理论基础

如图所示:队列是先进先出,栈是先进后出。

栈与队列理论1

232 用栈实现队列

image.png

因为缺乏基础,所以第一轮刷题先把代码看懂,先把意思理解。

栈是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()方法是返回队列开头的元素,

image.png

  1. ans = self.pop(): 这一步调用 pop() 方法获取队列前端的元素。pop() 方法会实际从 stack_out 移除这个元素。
  2. self.stack_out.append(ans): 由于我们只想查看元素而不是移除它,所以我们立即将刚刚"弹出"的元素重新添加到 stack_out 中。
  3. 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 用队列实现栈

image.png

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也就是获取队列中最后一个元素的时候,我们就可以先将队列中的最后一个元素之前的元素全部出队,然后重新入队,这样结束之后,在进行出队,就获取到的是最后一个元素。

优化,使用一个队列实现

  1. self.que = deque(): 创建一个新的空 deque 对象。
  2. self.que.append(x): 在 deque 的右端(末尾)添加一个元素。
  3. self.que.popleft(): 移除并返回 deque 最左端(开头)的元素。
  4. len(self.que): 返回 deque 的长度。
  5. 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 有效的括号

image.png

思路:使用栈的先进后出。

因为有三种类型的括号,但是都是由左右两种组成,所以说我们遇到左括号可以放进栈中右括号,如果遇到右括号,就可以和栈顶元素进行比较,如果相同,那么就出栈。

这是整体的思路,但是其实存在有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 删除字符串中的所有相邻重复项

image.png

感觉和括号的题目差不多。

思路:用栈来进行,如果遇到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 += 1return ''.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;

image.png