代码随想录算法训练营第11天|LeetCode150. 逆波兰表达式求值 ;LeetCode 239. 滑动窗口最大值 ;LeetCode347. 前 K 个高频元素
算法刷题笔记
1. LeetCode150. 逆波兰表达式求值
题目链接:https://leetcode.cn/problems/evaluate-reverse-polish-notation/description/
题目没什么难度代码如下:
点击查看代码
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<long long> st;
for(int i=0;i<tokens.size();i++){
if(tokens[i]=="+"||tokens[i]=="-"||tokens[i]=="*"||tokens[i]=="/"){
int nums1=st.top();
st.pop();
int nums2=st.top();
st.pop();
if(tokens[i]=="+") st.push(nums2+nums1);
if(tokens[i]=="-") st.push(nums2-nums1);
if(tokens[i]=="*") st.push(nums2*nums1);
if(tokens[i]=="/") st.push(nums2/nums1);
}
else st.push(stoll(tokens[i]));
}
int res=st.top();
st.pop();
return res;
}
};
注意两点:
- 对于字符的判断,因为
vector<string>& tokens所以要用双引号来判断,不能用单引号; - 对于
st.push(stoll(tokens[i])),要用 string to long long 来转换格式,否则push不进去。
2. LeetCode 239. 滑动窗口最大值
题目链接:https://leetcode.cn/problems/sliding-window-maximum/description/
<考察对队列的应用>
最开始只想把每一组的最大的存起来(只存最大的),然后与删除的与加入的比较,但是遇到要删除的是最大的,要加入的比最大的小就没办法了。
看完代码随想录后写出答案:
点击查看代码
class Solution {
private:
class Myque{
public:
deque<int> que;
void pop(int val){
if(!que.empty()&&val==que.front()){
que.pop_front();
}
}
void push(int val){
while(!que.empty()&&val>que.back()){
que.pop_back();
}
que.push_back(val);
}
int findmax(){
return que.front();
}
};
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
Myque que;
vector<int>res;
for(int i=0;i<k;i++){
que.push(nums[i]);
}
res.push_back(que.findmax());
for(int i=0;i<nums.size()-k;i++){
que.pop(nums[i]);
que.push(nums[i+k]);
res.push_back(que.findmax());
}
return res;
}
};
学到了单调队列,可以解决问题,把备选项递减存储;比最开始想的好,不仅仅存最大值,还存备选项。
学习单调队列的思路,关键就在于push操作,由于push的操作,使队列里的数都是递减的(将小的pop)将最大的存到que的front。
实现的过程中,对于deque的新建不够熟练,对deque的pop_front、push_front、pop_back、push_back、front()、back()的使用也不够熟练。
3. LeetCode347. 前 K 个高频元素
<优先级队列的应用>
这道题目主要涉及到如下三块内容:
- 要统计元素出现频率
- 对频率排序
- 找出前K个高频元素
首先统计元素出现的频率,这一类的问题可以使用map来进行统计。
对于排序有两种不同的方法:sort和优先级队列(堆)。
最开始想用哈希表+排序,但是不知道哈希表要怎么排序,就没有实现,写完之后问豆包写出来了,代码如下:
哈希表+sort排序 写法
点击查看代码
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
// ==========1. 哈希表统计频率==========
unordered_map<int,int> cnt;
for(int x:nums){
cnt[x]++;
}
// ==========2. 把键值对放进数组 ==========
//注意vector的定义方式,键值对写法
vector<pair<int,int>> vec;
//加 & 更快(推荐)
for(auto&p:cnt){
vec.push_back(p);
}
// ==========3. 按频率 降序 排序 ==========
//学习Lambda 表达式
sort(vec.begin(),vec.end(),[](const pair<int,int>&a,const pair<int,int>&b){return a.second>b.second;});
// ==========4. 取前k个 ==========
vector<int> res;
for(int i=0;i<k;i++){
res.push_back(vec[i].first);
}
return res;
}
};
Lambda 表达式理解
[]:固定开头,lambda 标志;(const pair<int,int>& a, const pair<int,int>& b):sort 每次拿两个元素过来给你比较,把第一个元素叫 a,第二个叫 b。pair<int,int>&类型匹配 vec 里的元素。const &:只读、不拷贝、效率高;{ return a.second > b.second; }:函数体,决定 a 和 b 谁放前面。如果a的频率大于b的频率,返回 true,a 放 b 前面。
等价老式比较函数写法:
点击查看代码
// 自己写一个比较函数
bool cmp(const pair<int,int>& a, const pair<int,int>& b)
{
// 按频率降序
return a.second > b.second;
}
// 调用sort
sort(vec.begin(), vec.end(), cmp);
哈希表+小顶堆 写法
点击查看代码
class Solution {
public:
// 小顶堆的比较规则:定义一个比较类
// 作用:让优先队列按照【出现频率从小到大】排列(堆顶是频率最小的元素)
class mycomparison{
public:
// 重载()操作符,用于优先级队列排序
// lhs:左元素 rhs:右元素
// 返回true代表:lhs 的优先级比 rhs 低(会放在下面)
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) {
// ========== 第一步:统计每个数字出现的频率 ==========
// 哈希表 key:数字 value:出现次数
unordered_map<int,int> map;
for(int i=0;i<nums.size();i++){
map[nums[i]]++;
}
// ========== 第二步:用大小为 k 的小顶堆筛选 topK ==========
// 定义小顶堆
// 元素类型:pair<数字,出现次数>
// 底层容器:vector
// 比较规则:我们自定义的 mycomparison(小顶堆规则)
priority_queue<pair<int,int>,vector<pair<int,int>>,mycomparison> pri_que;
//iterator 迭代器循环也可以写成C++11范围for
for (unordered_map<int, int>::iterator it = map.begin(); it != map.end(); it++){
pri_que.push(*it);
// 堆的大小超过 k 了,就弹出堆顶(频率最小的那个)
// 保证堆里永远只保留频率最大的 k 个元素
if(pri_que.size()>k){
pri_que.pop();
}
}
vector<int> result(k);
// ========== 第三步:把堆中元素取出,放到结果数组 ==========
// 小顶堆堆顶是频率最小的,所以要从后往前放数组
for(int i=0;i<k;i++){
result[i]=pri_que.top().first;
pri_que.pop();
}
return result;
}
};
在实现的过程中遇到的最大的问题就是不会自定义小顶堆规则,不会使用迭代器进行循环,上面的迭代器循环在C++11中可以写成:
点击查看代码
for (auto& p : cnt){
pri_que.push(p);
// 下同上面逻辑
}
为什么用小顶堆,不用大顶堆:
定义一个大小为k的大顶堆,在每次移动更新大顶堆的时候,每次弹出都把最大的元素弹出去了,无法保留前K个高频元素。
用小顶堆,每次将堆中频率最小的元素弹出,最后小顶堆里积累的就是前k个频率最大的元素。
时间复杂度:
- 哈希 + 排序:$O(n\log n)$
- 哈希 + 小顶堆:$O(n\log k)$
部分内容引自:代码随想录

浙公网安备 33010602011771号