C++算法训练营 Day11 栈与队列(2)
1.逆波兰表达式求值
给你一个字符串数组tokens
,表示一个根据逆波兰表示法表示的算术表达式。
请你计算该表达式。返回一个表示表达式值的整数。
注意:
有效的算符为 '+'
、'-'
、'*'
和'/'
。
每个操作数(运算对象)都可以是一个整数或者另一个表达式。
两个整数之间的除法总是 向零截断 。
表达式中不含除零运算。
输入是一个根据逆波兰表示法表示的算术表达式。
答案及所有中间计算结果可以用32 位
整数表示。
示例 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
- 解题思路:
逆波兰表达式即后缀表达式,其特点为:
1.运算符在操作数之后
2.无需括号即可明确运算顺序
3.适合栈结构计算
(1)创建存储操作数的栈。
// 使用 long long 类型栈防止溢出stack<long long> st;
(2)然后遍历 token所有值,若遇到操作数,就进行压栈处理;若遇到运算符,就进行弹出栈中所有操作数,然后进行运算,这里要注意:先弹出的操作数是后一项,即被减数或被除数。
// 遍历所有 tokenfor (int i = 0; i < tokens.size(); i++) {// 当前 token 是运算符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); // 加法else if (tokens[i] == "-") st.push(num2 - num1); // 减法(注意操作数顺序)else if (tokens[i] == "*") st.push(num2 * num1); // 乘法else if (tokens[i] == "/") st.push(num2 / num1); // 除法(注意操作数顺序)}// 当前 token 是操作数else {// 将字符串转换为 long long 并压栈st.push(stoll(tokens[i])); // stoll = string to long long}}
(3)遍历完成后,获取栈中操作数的值,对于栈中操作数弹不弹出都没影响。
// 获取最终结果long long result = st.top();st.pop(); // 清空栈(可选操作)return result; // 返回整型结果
完整代码如下:
class Solution {
public:int evalRPN(vector<string>& tokens) {// 使用 long long 类型栈防止溢出stack<long long> st;// 遍历所有 tokenfor (int i = 0; i < tokens.size(); i++) {// 当前 token 是运算符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); // 加法else if (tokens[i] == "-") st.push(num2 - num1); // 减法(注意操作数顺序)else if (tokens[i] == "*") st.push(num2 * num1); // 乘法else if (tokens[i] == "/") st.push(num2 / num1); // 除法(注意操作数顺序)}// 当前 token 是操作数else {// 将字符串转换为 long long 并压栈st.push(stoll(tokens[i])); // stoll = string to long long}}// 获取最终结果long long result = st.top();st.pop(); // 清空栈(可选操作)return result; // 返回整型结果}
};
2.滑动窗口最大值
给你一个整数数组nums
,有一个大小为k
的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的k
个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
示例 1:
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
示例 2:
输入:nums = [1], k = 1
输出:[1]
- 解题思路
本题利用到单调队列,所谓单调队列就是:维护一个元素值单调递减的双端队列(队头到队尾递减),队头元素始终是当前窗口的最大值,从而高效获取滑动窗口的最大值,避免每次遍历窗口。
(1)先初始化,创建单调队列
(2)填充初始窗口:将前k个元素加入队列,并记录初始最大值,即获取队列头部元素。
(3)滑动窗口:先移除窗口左侧元素(如果它仍是当前最大值),然后添加窗口右侧新元素(维护队列单调性),最后获取当前窗口最大值。
(4)返回结果。
class Solution {
private:// 自定义单调队列类class MyQueue {public:deque<int> que; // 双端队列作为底层存储// 移除元素(仅在元素等于队首时移除)void pop(int value) {// 只有当队列非空且队首等于要移除的值时才弹出if (!que.empty() && value == que.front()) {que.pop_front();}}// 添加元素(维护单调递减特性)void push(int value) {// 移除比当前值小的所有尾部元素while (!que.empty() && value > que.back()) {que.pop_back(); // 这些小的值不可能成为后面窗口的最大值}// 添加当前值到队尾que.push_back(value);}// 获取当前队列中的最大值(即队首元素)int 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.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;}
};
3.前 K 个高频元素
给你一个整数数组nums
和一个整数k
,请你返回其中出现频率前k
高的元素。你可以按任意顺序返回答案。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
- 解题思路:
执行过程示例
假设输入:nums = [1,1,1,2,2,3], k = 2
步骤1:统计频率
元素: 1 → 频率: 3
元素: 2 → 频率: 2
元素: 3 → 频率: 1
步骤2:构建小顶堆(维护K=2)
添加 [1:3] → 堆: [1:3]
添加 [2:2] → 堆: [2:2] [1:3](堆顶是2:2)
添加 [3:1] → 堆: [3:1] [1:3] [2:2] → 堆大小>2 → 弹出堆顶(3:1)
最终堆: [2:2] [1:3](堆顶是2:2)
步骤3:提取结果
倒序输出:
i=1: 堆顶2 → 结果[1]=2
i=0: 堆顶1 → 结果[0]=1
最终结果: [1,2]
class Solution {
public://自定义比较器类(小顶堆)class mycomparison {public://重载 () 运算符,用于比较两个 pairbool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {//比较频率值:lhs.second > rhs.second 返回 true//这样在优先队列中,频率较小的元素会被放在堆顶return lhs.second > rhs.second;}};vector<int> topKFrequent(vector<int>& nums, int k) {// 1.统计元素出现频率unordered_map<int, int> map; //存储<元素, 出现次数>for (int i = 0; i < nums.size(); i++) {map[nums[i]]++; //统计每个元素的出现次数}// 2.创建小顶堆存储 Top K 元素//参数说明://- pair<int, int>: 存储<元素, 频率>//- vector<pair<int, int>>: 底层容器//- mycomparison: 自定义比较器priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;//3.遍历频率表,维护大小为 K 的小顶堆for (auto it = map.begin(); it != map.end(); it++) {pri_que.push(*it); //将当前元素加入堆//堆大小超过 K 时,移除频率最小的元素if (pri_que.size() > k) {pri_que.pop(); //弹出堆顶(当前最小频率元素)}}//4.提取结果(倒序输出)vector<int> result(k);for (int i = k - 1; i >= 0; i--) {result[i] = pri_que.top().first; //获取元素值pri_que.pop(); //移除已处理元素}return result;}
};