代码随想录算法训练营第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;
    }
};

注意两点:

  1. 对于字符的判断,因为vector<string>& tokens所以要用双引号来判断,不能用单引号;
  2. 对于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_frontpush_frontpop_backpush_backfront()back()的使用也不够熟练。


3. LeetCode347. 前 K 个高频元素

<优先级队列的应用>

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

  1. 要统计元素出现频率
  2. 对频率排序
  3. 找出前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)$

部分内容引自:代码随想录

posted @ 2026-05-09 17:29  wh67  阅读(1)  评论(0)    收藏  举报