代码随想录算法训练营Day11 栈与队列的其他题目
栈与队列的其他经典题目
Leetcode 150. 逆波兰表达式求值
题目链接:https://leetcode.cn/problems/evaluate-reverse-polish-notation/description/
题目描述:给你一个字符串数组 tokens,表示一个根据 逆波兰表示法 表示的算术表达式。
请你计算该表达式。返回一个表示表达式值的整数。
注意:
- 有效的算符为
[+]、[-]、[*]和[/]。 - 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
- 两个整数之间的除法总是 向零截断。
- 表达式中不含除零运算。
- 输入是一个根据逆波兰表示法表示的算术表达式。
- 答案及所有中间计算结果可以用 32 位 整数表示。
题目解析:考虑用栈来模拟匹配的过程。遍历s的所有字符,如果遇到了运算符,就往前找两个数字进行计算,然后把结果放回去。
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> stk;
for (auto& s : tokens) {
if (s == "+" || s == "-" || s == "*" || s == "/") {
int b = stk.top(); stk.pop();
int a = stk.top(); stk.pop();
if (s == "+") stk.push(a + b);
else if (s == "-") stk.push(a - b);
else if (s == "*") stk.push(a * b);
else if (s == "/") stk.push(a / b);
} else {
stk.push(stoi(s));
}
}
return stk.top();
}
};
Leetcode 239. 滑动窗口最大值
题目链接:https://leetcode.cn/problems/sliding-window-maximum/description/
题目描述:给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值。
题目解析:滑动窗口板子题。
先说一下滑动窗口维护最值的原理。用一个很通俗的比方,如果有一个选手比你年轻,并且他比你更强,那么你永远不可能超过他。
对应到滑动窗口中,就是:如果一个元素比你晚进入窗口,并且他比你更大,那么你在窗口里,永远不可能是最优的。
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
int n = nums.size();
vector<int> ans(n - k + 1);
deque<int> q;
// 枚举每个元素
for (int i = 0; i < n; i ++) {
// 1.进入窗口
while (!q.empty() && nums[q.back()] <= nums[i]) {
q.pop_back();
}
q.push_back(i);
// 2.离开窗口
int left = i - k + 1;
if (q.front() < left) {
q.pop_front();
}
// 3.记录答案
if (left >= 0) {
ans[left] = nums[q.front()];
}
}
return ans;
}
};
Leetcode 347. 前 K 个高频元素
题目链接:https://leetcode.cn/problems/top-k-frequent-elements/description/
题目描述:给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
题目解析:首先我们需要得到每个元素出现的频率,考虑用unordered_map<int, int>记录。然后考虑桶排序,我们每个桶的编号是元素出现的频率,然后存放的是哪个元素。
// (法一:桶排序)
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
// cnt:记录频率
// max_cnt:便于桶排序
unordered_map<int, int> cnt;
int max_cnt = 0;
for (auto x : nums) {
cnt[x] ++;
max_cnt = max(max_cnt, cnt[x]);
}
// buckets:经典桶排序
vector<vector<int>> buckets(max_cnt + 1);
for (auto& [x, c] : cnt) {
buckets[c].push_back(x);
}
// 从大桶往小桶记录答案
vector<int> ans;
for (int i = max_cnt; i >= 0 && ans.size() < k; i --) {
ans.insert(ans.end(), buckets[i].begin(), buckets[i].end());
}
return ans;
}
};
我们还有另一种思路。我们先记录所有元素的频率,然后用priority_queue<pii>维护,最后只需要找到前k个就可以了。
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
// 记录频率
unordered_map<int, int> cnt;
for (auto x : nums) {
cnt[x] ++;
}
// 用priority_queue来维护小根堆
// cmp:如果cmp(a, b)为真,那么a的优先级比b高
// 但是因为是priority_queue,所以变成
// “如果cmp(a, b)为真,那么a的优先级比b高”
using pii = pair<int, int>;
auto cmp = [&](const pii& a, const pii& b) {
return a.second > b.second;
};
priority_queue<pii, vector<pii>, decltype(cmp)> pq(cmp); // 不要忘记构造的时候传(cmp)
for (auto& [x, c] : cnt) {
pq.emplace(x, c);
// 只要k个最好的,大于k了,就从最差的开始丢
if (pq.size() > k) {
pq.pop();
}
}
// 记录答案
vector<int> ans(k);
for (int i = 0; i < k; i ++) {
auto [x, c] = pq.top();
pq.pop();
ans[i] = x;
}
return ans;
}
};
顺带一提,这样的时间复杂度是O(nlogk)的,而法一是更优的O(n)做法。
之前集训贪心的时候,我一直不会用匿名函数的写法,用仿函数我觉得太麻烦了。搞得我都是把数据倒过来,然后用自带的排序……

浙公网安备 33010602011771号