【代码随想录训练营第42期 Day11打卡 LeetCode 150. 逆波兰表达式求值 239. 滑动窗口最大值 347.前 K 个高频元素

目录

一、做题心得

二、题目与题解

题目一:150. 逆波兰表达式求值 

题目链接

题解

题目二:239. 滑动窗口最大值

题目链接

题解

题目三:347.前 K 个高频元素

题目链接

题解

三、小结


一、做题心得

今天是代码随想录打卡的第11天,迎来了栈与队列章节的part2,涉及到了优先队列的一些问题。当然,有了昨天回顾的很多基础,个人感觉今天的题难度不算大,不过有些细节容易出错,等会会通过题目慢慢分析。

话不多说,直接看题。

二、题目与题解

题目一:150. 逆波兰表达式求值 

题目链接

150. 逆波兰表达式求值 - 力扣(LeetCode)

给你一个字符串数组 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 + 4 ) 。
  • 该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) 。

逆波兰表达式主要有以下两个优点:

  • 去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。
  • 适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中

思路是不是已经很明显了,就是使用栈,遇到数字进栈,遇到运算符就把后进栈的两个数字拿出来计算,算完了再进栈(选后放进的两个元素是因为运算符是与靠它最近的前两个数字使用),这样一直慢慢计算直到遍历完既可。

这里有两个需要注意的点

1.题目规定tokens是string类型的数组,后续对于其每一个元素的使用,都是针对于字符串而言。举个例子,假使token[0]为+,判断的时候写token[0] == "+",注意这里是双引号,而不是单引号。

2.拿出来进行计算的两个数中,后拿出来的数b应该放在运算符前(后拿出来对应着先放进去)。

代码如下:

class Solution {
public:
    int evalRPN(vector<string>& tokens) {       //注意:tokens每个元素都是字符串(双引号)
        stack<int> st;
        for(int i = 0;i < tokens.size();i++)
        { 
            if(tokens[i] == "+" || tokens[i] == "-" || tokens[i] == "*" || tokens[i] == "/")   //可以用token快捷方便表示tokens[i]:for(const string& token : tokens) 
            {
                int a = st.top();
                st.pop();
                int b = st.top();
                st.pop();
                //下边的判断语句用switch()会显得更加简洁方便
                if(tokens[i] == "+")        //注意都是双引号,因为tokens是string类型数组
                    st.push(b + a);
                else if(tokens[i] == "-")
                    st.push(b - a);
                else if(tokens[i] == "*")          
                    st.push(b * a);
                else
                    st.push(b / a);
            }
            else{
                st.push(stoi(tokens[i]));       //字符串转数字函数
            }
        }
        return st.top();
    }
};

题目二:239. 滑动窗口最大值

题目链接

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

返回 滑动窗口中的最大值 

示例 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]

提示:

  • 1 <= nums.length <= 105
  • -104 <= nums[i] <= 104
  • 1 <= k <= nums.length
题解

首先说说纯暴力解法吧,很容易想到,但也的确很容易超时。

这里可以看看我的尝试(通过了部分案例):

这个题虽然显示的是困难,但只要你想到了优先队列的思想,你就能快速解决这道题。

先说说优先队列吧:优先队列(Priority Queue)是一种特殊的队列,它赋予队列中每个元素以优先级,并按照元素的优先级顺序进行出队操作。与普通队列“先进先出”(FIFO, First In First Out)的原则不同,优先队列遵循的是“最高优先级先出”(HPFO, Highest Priority First Out)的原则。这意味着,在优先队列中,具有最高优先级的元素会最先被移除。

简单来说,就是我们可以通过优先队列对队列进行排序,使队列按照从大到小或者从小到大顺序排序出队列(运用的是堆的思想)

大顶堆(最大的位于堆顶):priority_queue<int> q; (也可写作priority_queue<int,<vector<int>,less<int>> q;)  

小顶堆(最小的位于堆顶):priority_queue<int,<vector<int>,greater<int>> q;

有了这些知识,这里我们直接看代码:

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        int n = nums.size();
        priority_queue<pair<int, int>> q;      //优先队列:这里默认大顶堆(让优先级高的排在队列的前边,优先出队,此时堆顶即是最大值)
        for (int i = 0; i < k; i++) {       //初始化第一个窗口
            q.emplace(nums[i], i);      //emplace():插入元素(pair<int,int>型:fisrt存储值nums[i],second存储下标i)
        }
        vector<int> ans = {q.top().first};      //ans用来储存结果,初始化存储第一个窗口的最大值(堆顶即是最大值)
        for (int i = k; i < n; i++) {
            q.emplace(nums[i], i);          //加入新元素
            while (q.top().second <= i - k) {       // 如果堆顶元素的索引已经不在当前窗口内,则移除它  
                q.pop();
            }
            ans.push_back(q.top().first);
        }
        return ans;
    }
};

再说说出现的emplace()函数吧:

 emplace、emplace_front、emplace_back分别对应insert、push_front和push_back。

但emplace都是构造而不是拷贝元素。emplace成员会使用传递的参数在容器管理的内存空间中直接构造元素。若参数为空,则调用元素的默认构造函数。

总之就是当你使用优先队列插入元素时,可以直接使用emplace()插入元素。

题目三:347.前 K 个高频元素

题目链接

347. 前 K 个高频元素 - 力扣(LeetCode)

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

示例 1:

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

示例 2:

输入: nums = [1], k = 1
输出: [1]

提示:

  • 1 <= nums.length <= 105
  • k 的取值范围是 [1, 数组中不相同的元素的个数]
  • 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的
题解

哈希+优先队列

这个题让我想起了之前Day6打卡242. 有效的字母异位词 - 力扣(LeetCode)这道题,两者都是记录数组中元素出现的次数。所以这道题我们首先应该想到的就是哈希,通过哈希表来存储元素以及元素存在的次数(哈希表这块不会的话可以看看我之前day6的打卡)。不过到这里,我们就该思考了,怎样将这些数据排序并按照顺序输出,这里就又能想到优先队列的做法。

代码如下:

class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
        unordered_map<int,int> hash;        //元素值(first),元素出现次数(second),元素在前,次数在后
        priority_queue<pair<int,int>> q;    //大顶堆:将元素出现次数从大到小排序(first),元素值(second),次数在前,元素在后
        for(int i : nums){        //遍历nums,存储数据到哈希数组中
            hash[i]++;
        }
        vector<int> ans;
        for(auto p : hash) {       //遍历整个哈希数组
            q.emplace(p.second, p.first);       //注意这里pair的顺序,插进队列时:次数在前,元素在后
        }  
        while(k--){
            ans.push_back(q.top().second);      //存入元素(second)
            q.pop();    //存完一个出一个
        }
        return ans;
    }
};

这里要注意一点:在这句代码中

for(auto p : hash) {     
            q.emplace(p.second, p.first);  
        }

这里得用auto,而不能用int。因为int类型是一个整数类型,而unordered_map的迭代器在遍历时会返回指向其内部元素的迭代器,这些元素是pair<const int, int>类型的。因此,如果您尝试将auto替换为int,类型将不匹配,因为int无法直接存储或表示一个键值对。

三、小结

今天的打卡到此也就结束了,最大的收获就是了解了优先队列的使用以及auto的作用,明天再继续加油。最后,我是算法小白,但也希望终有所获。

  • 21
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值