目录
LeetCode150.逆波兰表达式求值
给你一个字符串数组
tokens
,表示一个根据 逆波兰表示法 表示的算术表达式。请你计算该表达式。返回一个表示表达式值的整数。
注意:
- 有效的算符为
'+'
、'-'
、'*'
和'/'
。- 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
- 两个整数之间的除法总是 向零截断 。
- 表达式中不含除零运算。
- 输入是一个根据逆波兰表示法表示的算术表达式。
- 答案及所有中间计算结果可以用 32 位 整数表示。
思路:这道题考察对栈的使用,当遍历元素为运算符时需要将栈中的两个操作数弹出,运算完结果再压入栈,直到循环结束,将栈里面仅剩的一个元素保存输出即可。下面提供了两种方式,一种是自定义将字符串类型的栈中的元素转换为整型类的变量,一种是直接使用内置函数转换。
//将栈里面的字符串转换为整型int,使用long返回防止超限
long stringToInt(string& s){
long result = 0;
bool minus = 0;
int index = 0;
if(s[0] == '-'){
index = 1;
minus = 1;
}//当是负数时设置minus=1标志位
int size = s.size();
if(minus == 1) size --;//负数的话将其当成正数处理,然后再以负数返回
for(; index < s.size(); index ++, size --){
result += (s[index] - '0') * std::pow(10, size - 1);
}
if(minus == 1){
return 0 - result;
}else{
return result;
}
}
int evalRPN(vector<string>& tokens) {
stack<string> st;
int op1,op2;
int result = 0;
for(int i = 0; i < tokens.size(); i ++){
if(tokens[i] == "+"){
op1 = stringToInt(st.top());
//当遇到对应操作符时取栈顶第一、二操作数
//注意操作顺序
st.pop();
op2 = stringToInt(st.top());
st.pop();
st.push(to_string(op2 + op1));
continue;
}else if(tokens[i] == "-"){
op1 = stringToInt(st.top());
st.pop();
op2 = stringToInt(st.top());
st.pop();
st.push(to_string(op2 - op1));
continue;
}else if(tokens[i] == "*"){
op1 = stringToInt(st.top());
st.pop();
op2 = stringToInt(st.top());
st.pop();
st.push(to_string(op2 * op1));
continue;
}else if(tokens[i] == "/"){
op1 = stringToInt(st.top());
st.pop();
op2 = stringToInt(st.top());
st.pop();
st.push(to_string(op2 / op1));
continue;
}else{
st.push(tokens[i]);
}
}
string str = st.top();//最后统一取栈里面的最后一个元素,将其转换位整型数返回即可
result = stringToInt(str);
return result;
}
int evalRPN(vector<string>& tokens) {
stack<int> st;
int op1,op2;
for(int i = 0; i < tokens.size(); i ++){
if(tokens[i] == "+" || tokens[i] == "-" || tokens[i] =="*" || tokens[i] == "/"){
op1 = st.top();
st.pop();
op2 = st.top();
st.pop();
//当遇到对应操作符时取栈顶第一、二操作数
//注意操作顺序v
if(tokens[i] == "+") st.push(op2 + op1);
if(tokens[i] == "-") st.push(op2 - op1);
if(tokens[i] == "*") st.push(op2 * op1);
if(tokens[i] == "/") st.push(op2 / op1);
}else{
st.push(stoi(tokens[i]));//将字符串转换为int型整数
}
}
int result = st.top();
return result;
}
时间复杂度:O(n)
空间复杂度:O(n)
LeetCode239.滑动窗口最大值
给你一个整数数组
nums
,有一个大小为k
的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的k
个数字。滑动窗口每次只向右移动一位。返回 滑动窗口中的最大值 。
思路:这道题采用单调队列,所谓单调不是指将元素全部排好序,如果是这样,那就直接排序就好了,没必要再采用单调队列法了。
这里是使用单调队列的整体思想,因为其具体实现与具体情况有关,并没有固定写法。
这里采用了deque作为底层的单调队列的实现容器,deque是可以两边进两边出的一种队列。
在这里主要是实现一种队列,对于窗口的窗尾元素删除,对窗头元素加入,然后还有是将每个窗口区间的最大值保存其值,主要完成这三件事。
对于窗口元素的删除,是在窗口元素与单调队列的队首元素相等时删除,(因为当不是队首时,会因为其值比其他元素小,在添加进队列时早就删除);
对于窗头元素的添加,将所有小于它的单调队尾元素删除队列,直到遇见比起大的。每个元素都会进入队列,出队列,但什么时候出,就看时机了。
对于保存每个窗口区间最大值,每次只需在删除、添加后,将单调队列首部元素保存即可,因为这必然是目前窗口内最大的。
我们的目的不是将窗口内每个元素排序,而是每次试图找到能够成为最大值的元素,将其加入,其他的在加入后,也会因为其太小而被后续删除。
class Solution {
private:
class MyQueue{
public:
deque<int> que;//使用deque充当单调队列
//自定义pop函数,当窗口移动时,如果说当前元素的前第k-3个元素为单调队列的队头元素,直接pop掉,
//因为窗口之后算最大值已经使用不到它了,有了一个新区间,即使它很大,
void pop(int value){
if(!que.empty() && value == que.front()){
que.pop_front();
}
}
//自定义push函数,当要加入的值比单调队列里面的后面的元素大时,将它们pop_back()掉,
//说明在窗口移动至应该添加该元素时,之前的比其小的元素显然在其存在这个队列中时,不再是最大的值了,
//否则的话,直接将其加入
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> res;
for(int i = 0; i < k; i ++){
que.push(nums[i]); //先将前k个元素加入到单调队列中
}
res.push_back(que.front());//将前k个元素的最大值加入到res中
for(int i = k; i < nums.size(); i ++){
que.pop(nums[i - k]);//首先对滑出去的之前的窗头元素删除
que.push(nums[i]);//加入新滑入的元素
res.push_back(que.front());//加入该元素后,说明内部已经比较完毕,往res中加入单调队列最大值front处的值
}
return res;
}
};
时间复杂度:O(n)
空间复杂度:O(k)
这里还有一种方式是使用multiset,因为其本身是有序,并且允许重复值的一种set类型,每次只需将窗尾元素删除,将窗头元素加入,它就自动排序,然后找到其最大值加入即可。但是时间消耗比上面的单调队列来说要复杂,别看其代码精简,erase函数和find函数都是时间复杂度为O(n)的,所以该方法接近时间复杂度为O(n^2)。
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
multiset<int> mset;
vector<int> res;
for(int i = 0; i < nums.size(); i ++){
if(i >= k) mset.erase(mset.find(nums[i - k]));
//注意这里删除需要使用find方法删除找到的第一个元素,
//否则可能因为编译器和STL的实现的不同,将所有找到的元素都删除了,会出错
mset.insert(nums[i]);
if(i >= k - 1) res.push_back(*mset.rbegin());
}
return res;
}
LeetCode347.前k个高频元素
给你一个整数数组
nums
和一个整数k
,请你返回其中出现频率前k
高的元素。你可以按 任意顺序 返回答案。
思路:这道题有两种解法,一种是辅助数组法,一种是优先级队列法。
1、辅助数组法
说是辅助数组,起始是辅助map,大体思路就是先使用一个key不重复的map统计情况,然后用一个有序的且key值允许重复的map将统计情况中的key和value互换,这样排好序后,从后面开始,逆着找,则能找到前k个出现频率高的元素。
vector<int> topKFrequent(vector<int>& nums, int k) {
map<int, int> mp;//key不可以重复
multimap<int,int> mp_r;//key可以重复
vector<int> res;
for(int item : nums){
mp[item] ++;//将数组情况放入
}
for(auto it = mp.begin(); it != mp.end(); it ++){
mp_r.insert(make_pair(it -> second, it -> first));
//将mp中的key和value值反过来放在mp_r中
}
for(auto iter = mp_r.rbegin(); k > 0; iter ++, k --){
res.push_back(iter -> second);//倒序找到出现频率前k高的元素
}
return res;
}
时间复杂度:O(n)
空间复杂度:O(n)
2、优先级队列法
优先级队列是一种完全二叉树,根据其实现情况可以分为最大堆和最小堆情况。
本题采用的是实现最小堆情况,因为最大堆每次弹出的都是当前最大值,后面没有办法返回题目要求的内容。
当使用了优先级队列后,维护队列的大小为k个,根据自定义的比较类实现它们按照出现次数的多少排序,最后统计完成,输出即可。
//自定义比较类
class MyComparison{
public:
bool operator()(const pair<int, int>& left, const pair<int, int>& right){
return left.second > right.second; //构造较小堆
}
};
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int, int> mp; //key不重复
for(int item: nums){
mp[item] ++; //统计频率
}
priority_queue<pair<int, int>, vector<pair<int, int>>, MyComparison> pri_que; //定义优先队列
for(unordered_map<int, int>::iterator it = mp.begin(); it != mp.end(); it ++){
pri_que.push(*it);//将mp中的统计频率放入优先队列
if(pri_que.size() > k){//维护优先队列的大小为k
pri_que.pop();
}
}
vector<int> res(k);
for(int i = 0; i < k; i ++){
res[i] = pri_que.top().first;//将优先队列里面的统计对的统计元素保存
pri_que.pop();
}
return res;
}
时间复杂度:O(nlogk)(排序是logk时间复杂度,整个过程是nlogk)
空间复杂度:O(n)
感谢你的阅读,希望我的文章能够给你帮助,如果有帮助,麻烦点赞加收藏,或者点点关注,非常感谢。
如果有什么问题欢迎评论区讨论!