代码随想录训练营【栈与队列篇】

栈与队列

注:本文代码来自于代码随想录

1.用栈实现队列 232

力扣232

C++

class MyQueue {
public:
    stack<int> stIn; //两个 int 类型的栈,分别用来处理队列的输入和输出。
    stack<int> stOut;
    /** Initialize your data structure here. */
    MyQueue() {

    }// 这是类的构造函数,用于初始化对象。在这里没有特别的初始化操作,所以是空的。
    /** Push element x to the back of queue. */
    void push(int x) {
        stIn.push(x);
    }

    /** Removes the element from in front of queue and returns that element. */
    int pop() {
        // 只有当stOut为空的时候,再从stIn里导入数据(导入stIn全部数据)
        if (stOut.empty()) {
            // 从stIn导入数据直到stIn为空
            while(!stIn.empty()) {
                stOut.push(stIn.top());
                stIn.pop();
            }
        }
        int result = stOut.top();
        stOut.pop();
        return result;
    }
    /*首先检查 stOut 是否为空。如果为空,就将 stIn 栈中的所有元素转移到 stOut 中,这样就可以实现队列的顺序。
从 stOut 栈顶弹出一个元素并返回。result 存储弹出的元素,stOut.pop() 删除这个元素。*/

    /** Get the front element. */
    int peek() {
        int res = this->pop(); // 直接使用已有的pop函数
        stOut.push(res); // 因为pop函数弹出了元素res,所以再添加回去
        return res;
    }
    /*使用 pop 方法获取队列头部的元素并存储在 res 中。
将 res 重新推回 stOut 栈中,因为 pop 方法会弹出元素。
返回 res*/

    /** Returns whether the queue is empty. */
    bool empty() {
        return stIn.empty() && stOut.empty();
    }
    /*empty 方法用于检查队列是否为空。只有当 stIn 和 stOut 都为空时,队列才为空,返回 true。否则返回 false。*/
};

Python

需要注意区分Python的pop()和我们自定义的pop()!!!

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:
            # 如果 stack_out 有元素,直接从 stack_out 中弹出并返回一个元素。这里的 pop 是 Python 内置的列表的 pop 方法,用于移除并返回列表的最后一个元素。
            return self.stack_out.pop()
        else:
            # 如果 stack_out 没有元素,将 stack_in 的所有元素逐个弹出并压入 stack_out。然后,从 stack_out 中弹出并返回一个元素。
            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)

2.用队列实现栈 225

力扣225

可以用两个队列实现,代码见卡哥的代码随想录。

一个队列在模拟栈弹出元素的时候只要将队列头部的元素(除了最后一个元素外) 重新添加到队列尾部,此时再去弹出元素就是栈的顺序了。

C++

class MyStack {
public:
    queue<int> que;
    /** Initialize your data structure here. */
    MyStack() {

    }
    /** Push element x onto stack. */
    void push(int x) {
        que.push(x);
    }
    /** Removes the element on top of the stack and returns that element. */
    int pop() {
        int size = que.size();
        size--;
        while (size--) { // 将队列头部的元素(除了最后一个元素外) 重新添加到队列尾部
            que.push(que.front());
            que.pop();
        }
        int result = que.front(); // 此时弹出的元素顺序就是栈的顺序了
        que.pop();
        return result;
    }

    /** Get the top element. */
    int top() {
        return que.back();
    }

    /** Returns whether the stack is empty. */
    bool empty() {
        return que.empty();
    }
};

Python

掌握Python的deque()类型,掌握popleft()

1. popleft() 是 Python 内置的操作吗?是用来操作列表的吗?

popleft() 不是操作列表的内置方法,而是 collections.deque 对象的方法。Python 的内置列表没有 popleft 方法

deque 是双端队列(double-ended queue)的缩写,是 Python collections 模块中提供的一种数据结构,支持在两端高效地添加和删除元素。dequepopleft() 方法用于移除并返回队列左端(也就是队首)的元素。

2. deque 是什么数据结构?

deque(双端队列)是一种通用的队列数据结构,支持在两端高效地添加和删除元素。它的特性包括:

  • 高效的添加和删除操作:在两端添加或删除元素的时间复杂度为 O(1)。
  • 灵活性:既可以用作队列(FIFO),也可以用作栈(LIFO)。

在 Python 中,dequecollections 模块的一部分,使用如下方式导入:

from collections import deque
3 代码里面的栈是什么,Python 真的有这个数据结构吗?

在这个代码中,我们用 deque 来模拟栈的行为。Python 本身没有专门的栈数据结构,但可以用列表或 deque 来实现栈的功能。栈是一种 LIFO(后进先出)数据结构,通常支持以下操作:

  • push:添加元素到栈顶。
  • pop:移除并返回栈顶元素。
  • peek(或 top):返回栈顶元素但不移除它。
  • empty:检查栈是否为空。

在 Python 中,列表可以用作栈,使用 append()pop() 方法来实现 pushpop 操作,但 deque 更高效。

注意:

append 方法默认将元素添加到队尾(右边)。

popleft 方法默认从队首(左边)移除并返回元素。

deque 中的队首和队尾
  1. 队首(Head)
    • deque 的队首默认在左边。
    • 在左边进行的操作,比如 appendleft()popleft(),是针对队首的。
  2. 队尾(Tail)
    • deque 的队尾默认在右边。
    • 在右边进行的操作,比如 append()pop(),是针对队尾的。
from collections import deque

dq = deque([1, 2, 3, 4, 5])
print(dq)  # 输出:deque([1, 2, 3, 4, 5])

dq.popleft()  # 移除并返回左端元素 1
print(dq)  # 输出:deque([2, 3, 4, 5])

dq.pop()  # 移除并返回右端元素 5
print(dq)  # 输出:deque([2, 3, 4])
class MyStack:

    def __init__(self):
        self.que = deque()
        """
        初始化了一个 deque 类型的成员变量 self.que。deque 是 Python 集合模块中的双端队列(double-ended queue),它支持在两端快速添加和删除元素。
        """

    def push(self, x: int) -> None:
        self.que.append(x)

    def pop(self) -> int:
        # 移除并返回栈顶元素
        if self.empty():
            # 检查队列是否为空,如果为空返回 None
            return None
        for i in range(len(self.que)-1):
            """
            将队列中的元素(除了最后一个元素)依次移到队列的右端。这是通过循环 len(self.que) - 1 次实现的,每次将队首元素移到队尾(self.que.popleft() 移除队首元素并返回,self.que.append() 将其添加到队尾)
            """
            self.que.append(self.que.popleft())
        # 最后一个元素此时在队首,调用 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()
        # 弹出最后一个元素并存储在 temp 中,将其重新添加到队列末尾,返回 temp
        self.que.append(temp)
        return temp

    def empty(self) -> bool:
        # 检查栈是否为空。
        return not self.que

3.有效的括号 20

力扣20

括号匹配是使用栈解决的经典问题。

有三种不匹配的情况:

  1. 第一种情况,字符串里左方向的括号多余
  2. 第二种情况,括号没有多余,但是 括号的类型没有匹配上
  3. 第三种情况,字符串里右方向的括号多余

使用栈的方式:扫描到左括号,就让对应的右括号入栈。扫描到右括号,就在栈里查找,栈顶的元素是不是它,相同就出栈,不相同则为不匹配。

动画见代码随想录网站本题解析

第一种情况:已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false

第二种情况:遍历字符串匹配的过程中,发现栈里没有要匹配的字符。所以return false

第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号return false

那么什么时候说明左括号和右括号全都匹配了呢,就是字符串遍历完之后,栈是空的,就说明全都匹配了。

C++

class Solution {
public:
    bool isValid(string s) {
        if (s.size() % 2 != 0) return false; // 如果s的长度为奇数,一定不符合要求
        stack<char> st;
        for (int i = 0; i < s.size(); i++) {
            if (s[i] == '(') st.push(')');
            else if (s[i] == '{') st.push('}');
            else if (s[i] == '[') st.push(']');
            // 第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号 return false
            // 第二种情况:遍历字符串匹配的过程中,发现栈里没有我们要匹配的字符。所以return false
            else if (st.empty() || st.top() != s[i]) return false;
            else st.pop(); // st.top() 与 s[i]相等,栈弹出元素
        }
        // 第一种情况:此时我们已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false,否则就return true
        return st.empty();
    }
};

Python

仅使用栈,更省空间

class Solution:
    def isValid(self, s: str) -> bool:
        stack = []
        
        for item in s:
            if item == '(':
                stack.append(')')
            elif item == '[':
                stack.append(']')
            elif item == '{':
                stack.append('}')
            elif not stack or stack[-1] != item:
               """ not stack:如果栈是空的,意味着没有匹配的左括号。
stack[-1] != item:如果栈顶元素(最近的左括号的对应右括号)不是当前字符,说明括号不匹配。"""
                return False
            else:
                """如果栈不为空且栈顶元素与当前右括号匹配,则弹出栈顶元素,继续检查下一个字符。"""
                stack.pop()
        
        return True if not stack else False
    # 如果所有字符处理完后,栈为空,说明所有括号匹配,返回 True;否则返回 False。
字典
class Solution:
    def isValid(self, s: str) -> bool:
        stack = []
        mapping = {
            '(': ')',
            '[': ']',
            '{': '}'
        }
        for item in s:
            if item in mapping.keys():
                stack.append(mapping[item])
            elif not stack or stack[-1] != item: 
                return False
            else: 
                stack.pop()
        return True if not stack else False

4.删除字符串中的所有相邻重复项 1047

力扣1047

C++

class Solution {
public:
    string removeDuplicates(string S) {
        stack<char> st;
        for (char s : S) {
            if (st.empty() || s != st.top()) {
                st.push(s);
            } else {
                st.pop(); // s 与 st.top()相等的情况
            }
        }
        string result = "";
        while (!st.empty()) { // 将栈中元素放到result字符串汇总
            result += st.top();
            st.pop();
        }
        reverse (result.begin(), result.end()); // 此时字符串需要反转一下
        return result;

    }
};

拿字符串直接作为栈,这样省去了栈还要转为字符串的操作。

class Solution {
public:
    string removeDuplicates(string S) {
        string result;
        for(char s : S) {
            if(result.empty() || result.back() != s) {
                result.push_back(s);
            }
            else {
                result.pop_back();
            }
        }
        return result;
    }
};

Python:

使用栈
# 方法一,使用栈
class Solution:
    def removeDuplicates(self, s: str) -> str:
        res = list()
        for item in s:
            if res and res[-1] == item:
                # 栈不为空而且栈顶元素与item相等,消除
                res.pop()
            else:
                res.append(item)
        return "".join(res)  # 字符串拼接
使用双指针模拟栈
# 方法二,使用双指针模拟栈,如果不让用栈可以作为备选方法。
class Solution:
    def removeDuplicates(self, s: str) -> str:
        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])

5.逆波兰表达式求值 150

力扣150

注意:题目要求结果是整数

逆波兰表达式主要有以下两个优点:

  • 去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。
  • 适合用栈操作运算:遇到数字则入栈;遇到运算符则取出栈顶两个数字进行计算,并将结果压入栈中
  • 逆波兰表达式相当于是二叉树中的后序遍历。 把运算符作为中间节点,按照后序遍历的规则可以画出一个二叉树。

C++

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        // 力扣修改了后台测试数据,需要用longlong
        stack<long long> st; 
        for (int i = 0; i < tokens.size(); i++) {
            if (tokens[i] == "+" || tokens[i] == "-" || tokens[i] == "*" || tokens[i] == "/") {
                long long num1 = st.top();
                st.pop();
                long long num2 = st.top();
                st.pop();
                if (tokens[i] == "+") st.push(num2 + num1);
                if (tokens[i] == "-") st.push(num2 - num1);
                if (tokens[i] == "*") st.push(num2 * num1);
                if (tokens[i] == "/") st.push(num2 / num1);
            } else {
                st.push(stoll(tokens[i]));
            }
        }

        int result = st.top();
        st.pop(); // 把栈里最后一个元素弹出(其实不弹出也没事)
        return result;
    }
};

Python

from operator import add, sub, mul

def div(x, y):
    # 使用整数除法的向零取整方式
    return int(x / y) if x * y > 0 else -(abs(x) // abs(y))

class Solution(object):
    op_map = {'+': add, '-': sub, '*': mul, '/': div}
    
    def evalRPN(self, tokens: List[str]) -> int:
        stack = []
        for token in tokens:
            if token not in {'+', '-', '*', '/'}:
                stack.append(int(token))
            else:
                op2 = stack.pop()
                op1 = stack.pop()
                stack.append(self.op_map[token](op1, op2))  # 第一个出来的在运算符后面
        return stack.pop()

另一种可行,但因为使用eval相对较慢的方法:

from operator import add, sub, mul

def div(x, y):
    # 使用整数除法的向零取整方式
    return int(x / y) if x * y > 0 else -(abs(x) // abs(y))

class Solution(object):
    op_map = {'+': add, '-': sub, '*': mul, '/': div}

    def evalRPN(self, tokens):
        """
        :type tokens: List[str]
        :rtype: int
        """
        stack = []
        for token in tokens:
            if token in self.op_map:
                op1 = stack.pop()
                op2 = stack.pop()
                operation = self.op_map[token]
                stack.append(operation(op2, op1))
            else:
                stack.append(int(token))
        return stack.pop()

return int(x / y) if x * y > 0 else -(abs(x) // abs(y))需要注意

int(x/y)是向0取整,x//y是向下取整,我们自定义的div是向0取整的整除法函数

6.滑动窗口最大值 239

力扣239

C++

class Solution {
private:
    class MyQueue { //单调队列(从大到小)
    public:
        deque<int> que; // 使用deque来实现单调队列
        // 每次弹出的时候,比较当前要弹出的数值是否等于队列出口元素的数值,如果相等则弹出。
        // 同时pop之前判断队列当前是否为空。
        void pop(int value) {
            if (!que.empty() && value == que.front()) {
                que.pop_front();
            }
        }
        // 如果push的数值大于入口元素的数值,那么就将队列后端的数值弹出,直到push的数值小于等于队列入口元素的数值为止。
        // 这样就保持了队列里的数值是单调从大到小的了。
        void push(int value) {
            while (!que.empty() && value > que.back()) {
                que.pop_back();
            }
            que.push_back(value);

        }
        // 查询当前队列里的最大值 直接返回队列前端也就是front就可以了。
        int front() {
            return que.front();
        }
    };
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        MyQueue que;
        vector<int> result;
        for (int i = 0; i < k; i++) { // 先将前k的元素放进队列
            que.push(nums[i]);
        }
        result.push_back(que.front()); // result 记录前k的元素的最大值
        for (int i = k; i < nums.size(); i++) {
            que.pop(nums[i - k]); // 滑动窗口移除最前面元素
            que.push(nums[i]); // 滑动窗口前加入最后面的元素
            result.push_back(que.front()); // 记录对应的最大值
        }
        return result;
    }
};

Python

from collections import deque


class MyQueue: #单调队列(从大到小
    def __init__(self):
        self.queue = deque() #这里需要使用deque实现单调队列,直接使用list会超时
    
    #每次弹出的时候,比较当前要弹出的数值是否等于队列出口元素的数值,如果相等则弹出。
    #同时pop之前判断队列当前是否为空。
    def pop(self, value):
        if self.queue and value == self.queue[0]:
            self.queue.popleft()#list.pop()时间复杂度为O(n),这里需要使用collections.deque()
            
    #如果push的数值大于入口元素的数值,那么就将队列后端的数值弹出,直到push的数值小于等于队列入口元素的数值为止。
    #这样就保持了队列里的数值是单调从大到小的了。
    def push(self, value):
        while self.queue and value > self.queue[-1]:
            self.queue.pop()
        self.queue.append(value)
        
    #查询当前队列里的最大值 直接返回队列前端也就是front就可以了。
    def front(self):
        return self.queue[0]
    
class Solution:
    def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
        que = MyQueue()
        result = []
        for i in range(k): #先将前k的元素放进队列
            que.push(nums[i])
        result.append(que.front()) #result 记录前k的元素的最大值
        for i in range(k, len(nums)):
            que.pop(nums[i - k]) #滑动窗口移除最前面元素
            que.push(nums[i]) #滑动窗口前加入最后面的元素
            result.append(que.front()) #记录对应的最大值
        return result

7.前 K 个高频元素 347

力扣347

C++

class Solution {
public:
    // 小顶堆
    class mycomparison {
    public:
        bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
            return lhs.second > rhs.second;
        }
    };
    vector<int> topKFrequent(vector<int>& nums, int k) {
        // 要统计元素出现频率
        unordered_map<int, int> map; // map<nums[i],对应出现的次数>
        for (int i = 0; i < nums.size(); i++) {
            map[nums[i]]++;
        }

        // 对频率排序
        // 定义一个小顶堆,大小为k
        priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;

        // 用固定大小为k的小顶堆,扫面所有频率的数值
        for (unordered_map<int, int>::iterator it = map.begin(); it != map.end(); it++) {
            pri_que.push(*it);
            if (pri_que.size() > k) { // 如果堆的大小大于了K,则队列弹出,保证堆的大小一直为k
                pri_que.pop();
            }
        }

        // 找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒序来输出到数组
        vector<int> result(k);
        for (int i = k - 1; i >= 0; i--) {
            result[i] = pri_que.top().first;
            pri_que.pop();
        }
        return result;

    }
};

Python

class Solution {
public:
    // 小顶堆
    class mycomparison {
    public:
        bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
            return lhs.second > rhs.second;
        }
    };
    vector<int> topKFrequent(vector<int>& nums, int k) {
        // 要统计元素出现频率
        unordered_map<int, int> map; // map<nums[i],对应出现的次数>
        for (int i = 0; i < nums.size(); i++) {
            map[nums[i]]++;
        }

        // 对频率排序
        // 定义一个小顶堆,大小为k
        priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;

        // 用固定大小为k的小顶堆,扫面所有频率的数值
        for (unordered_map<int, int>::iterator it = map.begin(); it != map.end(); it++) {
            pri_que.push(*it);
            if (pri_que.size() > k) { // 如果堆的大小大于了K,则队列弹出,保证堆的大小一直为k
                pri_que.pop();
            }
        }

        // 找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒序来输出到数组
        vector<int> result(k);
        for (int i = k - 1; i >= 0; i--) {
            result[i] = pri_que.top().first;
            pri_que.pop();
        }
        return result;

    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值