ch6_6逆波兰表达式 && ch6_7滑动窗口最大值

本文介绍了逆波兰表达式的求解方法,利用栈处理运算符和数字,以及C++实现的思路。同时,详细阐述了滑动窗口最大值问题,通过单调队列来维护窗口中的最大值,分析了单调队列的插入和删除操作。内容涵盖了字符串转换、栈操作和双端队列的使用等编程技巧。
摘要由CSDN通过智能技术生成

1. 逆波兰表达式

lc 150 逆波兰表达式

有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。

注意 两个整数之间的除法只保留整数部分。

可以保证给定的逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。

1.1 逆波兰表达式特点

  1. 遇到数字则入栈, 遇到算术符 则去除栈顶的两个数字;
  2. stoi 函数: 将传入的字符串 转为 int 类型;
int stoi (const string& str, size_t* idx = 0, int base = 10);
int stoi (const wstring& str, size_t* idx = 0, int base = 10);
所属头文件为string.h
参数:
str:表示所要转化的字符串
idx:表示想要str中开始转化的位置,默认为从第一个字符开始。
base:表示要用的进制(如2进制、16进制,默认为10进制)转化为int类型十进制数字。

1.2 步骤

  1. 新建 stack 容器适配器, 用于存放每两个数的运算结果

  2. 遍历字符型 vector 容器, tokens;
    2.1 如果遇到算术符:
    取出栈顶元素,作为数1, 移除栈顶元素;
    取出栈顶元素,作为数2, 移除栈顶元素;
    判断对应的运算符, 运算两个数; 注意到 先取出的数字,作为除数 和 减数;

    2.2 如果遇到数字, 则将 字符型的 数字 转为 整型的数字,然后压入到栈中;

  3. 取出栈中最后留下的数字(即最终算式的结果);

  4. 弹出栈中的元素;


#include "vector"
#include "string"
#include "stack"
using namespace  std;

class Solution{
public:
    int evalRPN(vector<string>& tokens){
        stack<int>  st;  // 新建 stack 容器适配器, 用于存放数字,和运算的中间结果;
        //  遍历字符串;
        for(int i = 0; i < tokens.size(); i++){
            // 判读是否为 运算符 还是 数字;
            if(tokens[i] == "*" || tokens[i] == "/" || tokens[i] == "+" || tokens[i] == "-"){
                // 如果是运算符,取出栈顶两个元素进行运算;
                int num1 = st.top();
                st.pop();
                int num2 = st.top();
                st.pop();
                if(tokens[i] == "*" )  st.push( num2 * num1);
                if(tokens[i] == "/" ) st.push(num2 / num1); //  注意除法, 先弹出来的作为除数;
                if( tokens[i] == "+" ) st.push( num2 + num1);
                if( tokens[i] == "-") st.push(num2 - num1);
            }
           // 若是数字, 直接将 字符型数值 转为 int型 数值, 压栈;
           else{ st.push(stoi(tokens[i]) );}
        }

        // 遍历结束后, 取出栈顶元素;
        int out = st.top();
        st.pop();
        return  out;

    }
};

2. 滑动窗口最大值;

lc 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]

2.1 涵盖知识点

  1. 序列式容器中 deque 的
    front() 方法: 返回第一个 元素的引用
    pop_front() 方法: 移除容器头部的元素;

back() 方法:返回容器最后一个元素的引用;
push_back() 方法: 在序列的尾部添加一个元素;
pop_back() 方法: 移除容器尾部的元素;

  1. 序列式容器 vector 中方法:
    push_back(): 在序列尾部添加一个元素;

  2. 单调队列 是双端队列中的 一种情况:
    单调队列中的元素按照递增或者递减的顺序;

2.2 分析问题;

长度为 N 的数组, 使用一个 长度为 K 的 窗口进行滑动, 每次窗口移动的步长 为stride , 每次保留当前窗口中最大值到结果集中, 然后移动;

此时:

输出结果集中元素的个数 = 总共滑动的窗口数 = N - k + stride;

2.3 单调队列的作用与维护

在每一次移动窗口的过程中, 需要对该单调队列进行维护:

  1. void pop(): 当队首元素满足条件时, 从队列中移除当前窗口中第一个元素, 以此来代表窗口向后移动了一个元素;

(满足的条件是: 队列中队首元素 == 当前窗口的第一个元素, 如果队列的首元素, 不等于 当前窗口的第一个元素, 则不移除元素; 为什么只有是首元素,才会移除呢? 因为 单调队列的首元素代表的是最大值, 即 如果当前窗口的首元素恰好是 最大值, 但是现在窗口向后移动了, 它不属于下一个窗口中的数值了, 因此它不能作为下一个窗口中的最大值而存在,应该去除掉;)

  1. void push(): 当队尾元素满足条件时,添加窗口外的后一个元素到队列中, 将当前窗口外的后面一个元素加入到队列尾部;

(单调队列需要满足的条件是: 队列尾部的元素需要大于等于 待存入的元素; 如果当前尾部的元素 小于 待存入元素, 则一直将队尾元素移除, 直到大于等于待存入元素为止 或者 队列中为空 )

  1. int front(): 取出当前队列中的最大值, 存入到结果集中, 表明移动窗口后,当前窗口的最大值;

2. 窗口中元素的表示

每次窗口移动时, 窗口的两种元素会与单调队列有关;

  1. 当前窗口中的第一个元素, 需要从队列中移除;
  2. 当前窗口外的后一个元素, 需要加入到队列中;

根据以上两种元素, 来维护更新该队列;

那么问题来了, 如何表示出 上面两种 窗口中的元素;
即移动窗口的过程中, 如何表示出
1. 当前 窗口中的第一个元素;
2. 当前 窗口外的后一个元素;

2.1 窗口元素的表示

k : 代表窗口的长度, 则下标 [0, k - 1] 代表第一个窗口内的元素 ,所在数组中N的下标。

  1. 当前窗口外的后一个元素表示:
    j:从 k 开始移动, j = k 便是第一个窗口向后移动时,待加入的元素;
    j: 代表当前窗口的后面一个元素下标, 即下一次移动窗口时, 该元素应该包含在下一个窗口中, 并且该元素需要被加入到队列中;

  2. 当前窗口的第一个元素表示:
    j - k : j = k 时, [j -k ] = [0], 便是第一个窗口中第一个元素, 在数组中的下标为0, 随着j 的增加,以此类推 , j -k 代表的 是后面每个窗口中的第一个元素 在数组中的 下标位置;

2.3 整体逻辑步骤

  1. 实例化一个单调队列;

  2. 新建一个 vector 容器, 用于每次窗口中的最大值;

  3. 先将数组中的前K 个元素, 存入到单端队列中;

  4. 将第一个窗口中的最大值 压入到结果集中;

  5. 从 第 K 个元素开始 继续遍历数组, 遍历数组过程中, 维护单调队列:
    5.1 将当前窗口的第一个元素从 单端队列中移除;
    5.2 将当前窗口外的后一个元素加入到队列中;
    5.3 将单端队列中的头部 最大值,压入到结果集中;

  6. 返回结果集;


#include "vector"
#include "deque"


using namespace std;


class Solution{
private:
    class MyQueue{  // 单调队列,
    public:
           deque<int>  que;
           // 当队列首部元素满足条件时,  从队列中移除 当前窗口的第一个元素;
           void pop(int value){ // 当队首元素 == 当前窗口的第一个元素时, 移除队列首部元素;
               if ( !que.empty() &&  que.front() == value )  que.pop_front();  //
           }

           //  队列尾部元素满足条件时, 将窗口外的后一个元素添加到队列中;
           void  push(int value){  // 当队尾元素大于 待加入的元素,压入待加入元素;  否则移除当前的队尾元素, 直到队列为空;
               while( !que.empty() && que.back() < value )  que.pop_back();
               que.push_back(value);  // 当队列为空, 或者队尾元素大于待加入元素, 压入待加入元素;
           }

           //  返回队列 队首元素的一个引用;
           int front(){  return  que.front();}

    };


public:
    vector<int>  maxSlidingWindow(vector<int>& nums, int k){
        // 1. 实例化自定义的单调队列;
        MyQueue  que;
        // 2. 新建一个 vector 容器,用于保存最大值的 结果集;
        vector<int>  result;

        // 3.  先将数组的第一个窗口中的数值 ,存入到 单调队列中;
        for( int i = 0; i < k; i++){
            que.push(nums[i]);
        }

        // 4. 保存第一个窗口中的最大值;
        result.push_back(que.front());  // 即将 队首元素加入到结果集中;

        // 5. 开始从 j = k 处, 遍历数组, 并在此过程中, 移动窗口, 维护队列, 保存最大值;
        for(int j = k; j < nums.size();  j++){
            // 先从队列中, 移除当前窗口的第一个元素;  [j - k]: 代表了每个窗口的第一个元素;
            que.pop( nums[j - k]);

            //  加入元素到队列中, 将当前窗口后的一个元素加入到队列中,  j: 代表了每个当前窗口的后一个元素;
            que.push(nums[j]);

            //  保留出当前窗口中的最大值;
            result.push_back(que.front());
        }
        return  result;
    }
    
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值