目录
一、做题心得
今天是代码随想录打卡的第11天,迎来了栈与队列章节的part2,涉及到了优先队列的一些问题。当然,有了昨天回顾的很多基础,个人感觉今天的题难度不算大,不过有些细节容易出错,等会会通过题目慢慢分析。
话不多说,直接看题。
二、题目与题解
题目一:150. 逆波兰表达式求值
题目链接
给你一个字符串数组
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 个高频元素
题目链接
给你一个整数数组
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的作用,明天再继续加油。最后,我是算法小白,但也希望终有所获。