150.逆波兰表达式求值
难度指数:😀🙂
这不仅仅是一道好题,也展现出计算机的思考方式
用栈来解决逆波兰表达式的计算过程。
题目链接:150.逆波兰表达式求值
逆波兰表达式就是一种后缀表达式
计算机在做运算的时候,用后缀表达式更加方便它的运算,因为计算机直接按照顺序去处理,不需要担心括号的优先级。
用栈来模拟后缀表达式的计算过程:
遇见数字就加入到栈里,遇见操作符就从栈里取出元素,进行计算后放回到栈里。
动画:
灵魂发问:为什么最后都计算出结果了,还给它加入到栈里?直接返回结果不香吗?
答:从代码整体上的简洁性来考虑,加入到栈里是可以的,省去最后做特殊处理的麻烦。
这道题,两个数字,遇见一个操作符就进行消除的操作,(和前两道题的思路大同小异)
栈这种数据结构非常适合做相邻字符的消除操作
代码思路:
遇见操作符,就从栈里取元素进行运算的操作。
stack<int> st;
for (i = 0; i < s.size(); i++) {
if (s[i] == '+' || s[i] == '-' || s[i] == '*' || s[i] == '/') {
nums1 = st.top(); //获取到栈顶元素
st.pop(); //将栈顶元素弹出
nums2 = st.top();
st.pop();
if (s[i] == '+') st.push(nums2 + nums1);
if (s[i] == '-') st.push(nums2 - nums1);
if (s[i] == '*') st.push(nums2 * nums1);
if (s[i] == '/') st.push(nums2 / nums1);
}
else st.push(s[i]);
int result = st.top();
st.pop();
return result;
}
说一嘴:题目给我们的逆波兰表达式一定是合法的表达式,我们这里没有对异常的情况做处理。
AC代码: (核心代码模式)
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> 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);
else if (tokens[i] == "-") st.push(nums2 - nums1);
else if (tokens[i] == "*") st.push((long)nums2 * nums1);
else if (tokens[i] == "/") st.push(nums2 / nums1);
}
else st.push(stoi(tokens[i])); //若遍历的是数字,push到栈里面
}
int result = st.top();
st.pop();
return result;
}
};
题目没啥难度,关键是理解逆波兰表达式是从哪里来的,就是从二叉树的后续表达式(后序遍历)来的。
239.滑动窗口最大值
难度指数:😀😕🙁😩
题目链接:239.滑动窗口最大值
题意:
给定一个数组,给出滑动窗口的范围,比如:k = 3,用这个滑动窗口去遍历这个数组,每次返回滑动窗口内的最大值。
很多人会有暴力出奇迹的想法,O(n × k)
更优的解法:
观察滑动窗口的移动过程,你会发现很像一个队列:
队列里面始终维护着这个窗口,每移动一个位置,就对应 pop 掉一个元素,同时 push 进来一个元素
pop();
push();
getMaxValue(); 每次调用这个函数,就询问这个队列里面当前的最大值是多少。
倘若队列里面能支持上面的操作,就很nice,但目前编程语言里还没有支持这个数据结构。
有同学会想:用优先级队列来解决这个问题
其实C++里面优先级队列这种数据结构就是大顶堆或者小顶堆,它只不过是单调递增或者单调递减。
……05:00~05:30
例如这个优先级队列,你就是要让它单调递减,那么内部实现就是一个大顶堆,(大顶堆就是一个将里面所有元素进行排序的二叉树,大顶堆的最大元素在根结点)
……
你要是使用优先级队列的话,你把元素加入到队列里面(队列会对元素进行排序,大顶堆就是单调递减),会导致元素的顺序改变了, pop 出来的的元素就不一定是滑动窗口里面真正要 pop 出来的元素。
⚠️不能使用优先级队列!
需要我们自己去实现一种队列,来解决这个问题:
单调队列: (单调队列就是维护队列里面的单调递增或者单调递减)
乍一看和优先级队列一样,其实不一样。
关键在于如何维护元素里面的单调递增和单调递减,如何加元素以及如何把这些元素pop出来,这个规则由我们自己定义。
相当于我们DIY了一种队列,维持这个队列里面的元素是单调递增或单调递减。
我们自定义队列,队列里维护的元素,就没有必要把窗口里的元素都添加进去,只需要维护有可能成为最大值的元素,放在队列的"出口"。
数组元素:1 3 -1 -3 5 3 2 1
如何维护? 模拟一下流程:
在3的前面,有比3小的弹出,
这样维护出口处是最大值,每次getMaxValue()就找出口处的元素就可以了。
动画:
代码思路:
AC代码: (核心代码模式)
class Solution {
private:
class MyQueue { //单调队列(单调递减)
public:
deque<int> que; //使用deque来实现单调队列
void pop(int value) {
if (!que.empty() && value == que.front()) {
que.pop_front();
}
}
void push(int value) {
while (!que.empty() && value > que.back()) {
que.pop_back();
}
que.push_back(value);
}
//查询当前队列里的最大值,直接返回队列的出口处元素
int front() {
return que.front();
}
};
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
MyQueue que;
vector<int> result;
for (int i = 0; i < k; i++) { //先将前k的元素放进队列
que.push(nums[i]);
}
result.push_back(que.front()); //result记录前k的元素的最大值
for (int i = k; i < nums.size(); i++) {
que.pop(nums[i - k]);
que.push(nums[i]);
result.push_back(que.front());
}
return result;
}
};
347.前K个高频元素
难度指数:😀😕🙁
这是用堆这种数据结构来解决的一道经典题目。
题目链接:347.前K个高频元素
主要有2个难点:
- 如何求数组中每个元素的频率?
- 如何对这个频率进行排序?
用map , value是频率;key是元素。
堆是一种二叉树的数据结构,每加入一个元素,它去调整的话就是O(logk), 我们只维护k个元素
代码思路:
//遍历数组,用map里面的value来统计频率
for (int i = 0; i < nums.size(); i++) {
map[nums[i]]++; //key这个元素所出现的频率,对应做++, nums[i]应该是这个key, 加上map[]就是它出现的频率
}
//我们应该定义一个优先级队列,应该定义一个小顶堆, 同时,我们仅仅对value进行排序
priority_queue()
同时,我们仅仅对value进行排序,我们在队列里放的元素是Pair这种数据结构,要存key和value
key是元素,value是元素出现过的频率,要对这个键值对进行排序。
不仅要对键值对进行排序,还需要控制如何进行排序:我们选择的是小顶堆
在优先级队列里面,我们要从小到大进行排序,那么就需要自己实现一个compare()
遍历map,用优先级队列把map里面所有的元素做遍历:
//it也是一个<key, value>键值对
for (map != it) {
que.push(it); //把一个键值对加入到优先级队列里
//优先级队列对k个元素进行排序
if (que.size() > k) {
que.pop(); //用小顶堆,每次pop的时候才能把最小的元素给pop走,堆里留下的就是较大的那些元素
}
}
题目想要先输出频率最高的元素,然后是频率次高的元素……
但是在优先级队列里面,它是先弹出频率相对较小的元素,然后才是频率较高的元素。
那么我们可以直接定义一个想要返回的数组result,然后倒序遍历数组。
vector<int> result;
for (int i = k - 1; i >= 0; i++) {
result[i] = que.top().first();
que.pop();
}
AC代码: (核心代码模式)
class Solution {
public:
//小顶堆
class mycomparison {
public:
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) {
//要统计元素出现概率
unordered_map<int, int> map; //map<nums[i], 对应出现的次数>
//遍历数组
for (int i = 0; i < nums.size(); i++) {
map[nums[i]]++;
}
//堆频率排序
//定义一个小顶堆,大小为k
priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;
//用固定大小为k的小顶堆,扫描所有频率的数值
for (unordered_map<int, int>::iterator it = map.begin(); it != map.end(); it++) {
pri_que.push(*it);
if (pri_que.size() > k) {
pri_que.pop();
}
}
// 找出前k个高频元素,因为小顶堆先弹出的是最小的,所以倒序来输出到数组
vector<int> result(k);
for (int i = k - 1; i >= 0; i--) {
result[i] = pri_que.top().first;
pri_que.pop();
}
return result;
}
};