【算法篇】栈与队列类(笔记)

目录

一、用栈实现队列

二、用队列实现栈

三、有效的括号

四、删除字符串中的所有相邻重复项

五、逆波兰表达式求值

六、滑动窗口最大值

[七、前 K 个高频元素](#七、前 K 个高频元素)


一、用栈实现队列

232. 用栈实现队列 - 力扣(LeetCode)https://leetcode.cn/problems/implement-queue-using-stacks/description/

请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(pushpoppeekempty):

实现 MyQueue 类:

  • void push(int x) 将元素 x 推到队列的末尾
  • int pop() 从队列的开头移除并返回元素
  • int peek() 返回队列开头的元素
  • boolean empty() 如果队列为空,返回 true ;否则,返回 false

说明:

  • 你 只能 使用标准的栈操作 ------ 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
  • 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
bash 复制代码
示例 1:
输入:
["MyQueue", "push", "push", "peek", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 1, 1, false]

解释:
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1
myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false
cpp 复制代码
class MyQueue {
public:
    // 定义栈
    stack<int> Instack;
    stack<int> Outstack;
    
    MyQueue() {
        
    }
    
    void push(int x) {
        Instack.push(x);
    }
    
    int pop() {
        if (Outstack.empty()) {
            // 输出栈为空
            while (!Instack.empty()) {
                Outstack.push(Instack.top());
                Instack.pop();
            }
        }
        int re = Outstack.top();
        Outstack.pop();
        return re;
    }
    
    int peek() {
        // 使用已有函数
        int re = this->pop();
        Outstack.push(re);
        return re;
    }
    
    bool empty() {
        return (Instack.empty() && Outstack.empty());
    }
};

/**
 * Your MyQueue object will be instantiated and called as such:
 * MyQueue* obj = new MyQueue();
 * obj->push(x);
 * int param_2 = obj->pop();
 * int param_3 = obj->peek();
 * bool param_4 = obj->empty();
 */

二、用队列实现栈

225. 用队列实现栈 - 力扣(LeetCode)https://leetcode.cn/problems/implement-stack-using-queues/description/

请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(pushtoppopempty)。

实现 MyStack 类:

  • void push(int x) 将元素 x 压入栈顶。
  • int pop() 移除并返回栈顶元素。
  • int top() 返回栈顶元素。
  • boolean empty() 如果栈是空的,返回 true ;否则,返回 false

注意:

  • 你只能使用队列的标准操作 ------ 也就是 push to backpeek/pop from frontsizeis empty 这些操作。
  • 你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
bash 复制代码
示例:
输入:
["MyStack", "push", "push", "top", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 2, 2, false]

解释:
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.top(); // 返回 2
myStack.pop(); // 返回 2
myStack.empty(); // 返回 False
cpp 复制代码
class MyStack {
public:
    queue<int> Inqueue;  // 单队列

    MyStack() {

    }
    
    void push(int x) {
        Inqueue.push(x);
    }
    
    int pop() {
        for (int i = 0; i < Inqueue.size() - 1; i++) {
            Inqueue.push(Inqueue.front());
            Inqueue.pop();
        }
        int res = Inqueue.front();
        Inqueue.pop();
        return res;
    }
    
    int top() {
        return Inqueue.back();
    }
    
    bool empty() {
        return Inqueue.empty();
    }
};

/**
 * Your MyStack object will be instantiated and called as such:
 * MyStack* obj = new MyStack();
 * obj->push(x);
 * int param_2 = obj->pop();
 * int param_3 = obj->top();
 * bool param_4 = obj->empty();
 */

三、有效的括号

20. 有效的括号 - 力扣(LeetCode)https://leetcode.cn/problems/valid-parentheses/description/

给定一个只包括 '('')''{''}''['']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。
  3. 每个右括号都有一个对应的相同类型的左括号。
bash 复制代码
示例 1:
输入: "()"
输出: true

示例 2:
输入: "()[]{}"
输出: true

示例 3:
输入: "(]"
输出: false

示例 4:
输入: "([)]"
输出: false

示例 5:
输入: "{[]}"
输出: true

思路:

本题遍历时存在三种情况:

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

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

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

cpp 复制代码
class Solution {
public:
    bool isValid(string s) {
        stack<char> str;
        for (int i = 0; i < s.length(); i++) {
            if (s[i] == '(') str.push(')');
            else if (s[i] == '{') str.push('}');
            else if (s[i] == '[') str.push(']');
            // 遍历的过程中栈已经为空,或者不匹配
            else if (str.empty() || s[i] != str.top()) return false;
            else str.pop();
        }
        return str.empty();
    }
};

四、删除字符串中的所有相邻重复项

1047. 删除字符串中的所有相邻重复项 - 力扣(LeetCode)https://leetcode.cn/problems/remove-all-adjacent-duplicates-in-string/description/

给出由小写字母组成的字符串 s,重复项删除操作会选择两个相邻且相同的字母,并删除它们。

s 上反复执行重复项删除操作,直到无法继续删除。

在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。

cpp 复制代码
示例:
输入:"abbaca"
输出:"ca"
解释:例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"。
cpp 复制代码
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;
    }
};

五、逆波兰表达式求值

150. 逆波兰表达式求值 - 力扣(LeetCode)https://leetcode.cn/problems/evaluate-reverse-polish-notation/description/

给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。

请你计算该表达式。返回一个表示表达式值的整数。

注意:

  • 有效的算符为 '+''-''*''/'
  • 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
  • 两个整数之间的除法总是 向零截断 。
  • 表达式中不含除零运算。
  • 输入是一个根据逆波兰表示法表示的算术表达式。
  • 答案及所有中间计算结果可以用 32 位 整数表示。
bash 复制代码
示例 1:
输入:tokens = ["2","1","+","3","*"]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9

示例 2:
输入:tokens = ["4","13","5","/","+"]
输出:6
解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6

示例 3:
输入:tokens = ["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
cpp 复制代码
class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<long long> record;
        for (int i = 0; i < tokens.size(); i++) {
            if (tokens[i] == "+" || tokens[i] == "-" || tokens[i] == "*" || tokens[i] == "/") {
                long long num1 = record.top(); record.pop();
                long long num2 = record.top(); record.pop();
                if (tokens[i] == "+") record.push(num2 + num1);
                if (tokens[i] == "-") record.push(num2 - num1);
                if (tokens[i] == "*") record.push(num2 * num1);
                if (tokens[i] == "/") record.push(num2 / num1);
            }
            else {
                record.push(stoll(tokens[i]));
            }
        }
        return record.top();
    }
};

六、滑动窗口最大值

239. 滑动窗口最大值 - 力扣(LeetCode)https://leetcode.cn/problems/sliding-window-maximum/description/

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值 。

bash 复制代码
示例 1:
输入: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

示例 2:
输入:nums = [1], k = 1
输出:[1]

思路:

这是使用单调队列的经典题目。

队列没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队列里的元素数值是由大到小的。C++中没有直接支持单调队列,需要我们自己来实现一个单调队列。

设计单调队列的时候,pop 和 push操作要保持如下规则:

  1. pop(value):如果窗口移除的元素value等于单调队列的出口元素,那么队列弹出元素,否则不用任何操作
  2. push(value):如果push的元素value大于入口元素的数值,那么就将队列入口的元素弹出,直到push元素的数值小于等于队列入口元素的数值为止

保持如上规则,每次窗口移动的时候,只要问 que.front() 就可以返回当前窗口的最大值。

具体的请查看网站: 代码随想录 (programmercarl.com)

cpp 复制代码
class Solution {
private:
    class MyQueue { //单调队列(从大到小)
    
public:
    deque<int> que; // 使用deque来实现单调队列
    
    void pop(int value) {
        // 如果弹出的数值等于队列出口元素的数值,则弹出
        if (!que.empty() && value == que.front()) {
            que.pop_front();
        }
    }
    
    void push(int value) {
        // 如果push的数值大于入口元素的数值,那么就将队列后端的数值弹出
        // 这样就保持了队列里的数值是单调从大到小的了
        while (!que.empty() && value > que.back()) {
            que.pop_back();
        }
        que.push_back(value);
    }
    
    int front() {
        // front为当前队列里的最大值
        return que.front();
    }
};

public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        MyQueue que;
        vector<int> result;

        // 先将前 k 的元素放进队列
        for (int i = 0; i < k; i++) { 
            que.push(nums[i]);
        }
        // result 记录前 k 的元素的最大值
        result.push_back(que.front()); 

        for (int i = k; i < nums.size(); i++) {
            que.pop(nums[i - k]); // 滑动窗口移除最前面元素
            que.push(nums[i]);    // 滑动窗口前加入最后面的元素
            result.push_back(que.front()); // 记录对应的最大值
        }
        return result;
    }
};

七、前 K 个高频元素

347. 前 K 个高频元素 - 力扣(LeetCode)https://leetcode.cn/problems/top-k-frequent-elements/description/

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

bash 复制代码
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]

示例 2:
输入: nums = [1], k = 1
输出: [1]

思路:

这道题目主要涉及到如下三块内容:

  1. 要统计元素出现频率
  2. 对频率排序
  3. 找出前K个高频元素

首先统计元素出现的频率,这一类的问题可以使用map来进行统计。

然后是对频率进行排序,这里我们可以使用一种 容器适配器就是优先级队列。

缺省情况下priority_queue利用max-heap(大顶堆)完成对元素的排序,这个大顶堆是以vector为表现形式的complete binary tree(完全二叉树)。

这种场景下,我们只需要维护 k 个 有序的序列 就可以了,所以 使用优先级队列是最优的。

要用小顶堆,因为要统计最大前k个元素,只有小顶堆每次将最小的元素弹出,最后小顶堆里积累的才是前k个最大元素。

cpp 复制代码
class Solution {
public:
    struct sortmap {
        bool operator() (pair<int, int> x, pair<int, int> y) {
            return x.second > y.second;
        }
    };

    vector<int> topKFrequent(vector<int>& nums, int k) {
        // 记录频数
        unordered_map<int, int> map;
        for (int i = 0; i < nums.size(); i++) {
            map[nums[i]]++;
        }
        // 排序,小顶堆维护前 k 个 map
        priority_queue<pair<int, int>, vector<pair<int, int>>, sortmap> record;
        for (unordered_map<int, int>::iterator it = map.begin(); it != map.end(); it++) {
            record.push(*it);
            if (record.size() > k) { 
                record.pop();
            }
        }
        vector<int> result(k);
        for (int i = k - 1; i >= 0; i--) {
            result[i] = record.top().first;
            record.pop();
        }
        return result;
    }
};
相关推荐
刚学HTML23 分钟前
leetcode 05 回文字符串
算法·leetcode
蜀黍@猿27 分钟前
【C++ 基础】从C到C++有哪些变化
c++
Am心若依旧40928 分钟前
[c++11(二)]Lambda表达式和Function包装器及bind函数
开发语言·c++
zh路西法38 分钟前
【C++决策和状态管理】从状态模式,有限状态机,行为树到决策树(一):从电梯出发的状态模式State Pattern
c++·决策树·状态模式
AC使者42 分钟前
#B1630. 数字走向4
算法
冠位观测者1 小时前
【Leetcode 每日一题】2545. 根据第 K 场考试的分数排序
数据结构·算法·leetcode
轩辰~1 小时前
网络协议入门
linux·服务器·开发语言·网络·arm开发·c++·网络协议
lxyzcm1 小时前
C++23新特性解析:[[assume]]属性
java·c++·spring boot·c++23
蜀黍@猿2 小时前
C/C++基础错题归纳
c++
古希腊掌管学习的神2 小时前
[搜广推]王树森推荐系统笔记——曝光过滤 & Bloom Filter
算法·推荐算法