C++刷题笔记(14)——leetcode150、239

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

在这里插入图片描述
逆波兰表达式又叫做后缀表达式,即将运算符写在操作数之后,是一种没有括号、严格遵循从左到右运算的后缀式表达方法。

表达式E的后缀形式
如果E是E1 op E2形式的表达式,这里op是任何二元操作符,则E的后缀式为E1’E2’ op,这里E1’和E2’分别为E1和E2的后缀式。

如:(1 + 2) * (3 + 4)写成后缀形式为1 2 + 3 4 + *

解法:栈

逆波兰表达式适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算(先出栈的是右操作数,后出栈的是左操作数),并将结果压入栈中。

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 rightnum = st.top();
				st.pop();
				int leftnum = st.top();
				st.pop();
				if (tokens[i] == "+") st.push(leftnum + rightnum);
				if (tokens[i] == "-") st.push(leftnum - rightnum);
				if (tokens[i] == "*") st.push(leftnum * rightnum);
				if (tokens[i] == "/") st.push(leftnum / rightnum);
			}
			else {
				st.push(stoi(tokens[i]));   //stoi:数字字符串转换成int输出
			}
		}
		return st.top();
	}
};

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

在这里插入图片描述

解法一:优先队列

C++ STL priority_queue容器适配器详解

优先队列priority_queue遵循"first in,largest out"原则,先进队列的元素并不一定先出队列,而是优先级最大的元素先出队列,默认按照元素从大到小进行排序。

优先级队列可以理解为披着队列外衣的堆,堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。 如果父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆。

采用最大堆的数据结构来保存元素,堆顶元素即为当前堆的最大值,并判断当前堆顶元素这是否在窗口中,在则直接返回,不在则删除堆顶元素并调整堆。
在这里插入图片描述
窗口遍历数组
在这里插入图片描述

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int> result;
        priority_queue<pair<int, int>> pq;

        for (int i = 0; i < k; i++) {                      //将数组的前k个元素放入优先队列中
            pq.push(make_pair(nums[i], i));                //或pq.emplace(nums[i], i);
        }
        result.push_back(pq.top().first);                  //堆顶就是最大元素

        for (int i = k; i < nums.size(); i++) {            //滑动窗口从索引为k的元素开始遍历,将新进入滑动窗口的元素加堆中
            pq.push(make_pair(nums[i], i));               
            while (pq.top().second <= i - k) {             //当堆顶元素不在滑动窗口中的时候,不断删除堆顶堆元素,直到最大值在滑动窗口里
                pq.pop();
            }
            result.push_back(pq.top().first);
        }
        return result;
    }
};

解法二:单调队列(双端队列)——最优解

单调队列即队列中所有的元素都是单调的,要么单增要么单减;
新增元素时添加到队尾,若队列不符合单调性就将队尾元素出队,直到队列单调或空。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
之后的遍历同理。

注意,队列中保存的并不是数,而是数组中该数的下标

还要考虑当队首元素不在滑动窗口时的情况:
在这里插入图片描述
L、R来表示窗口的左边界和右边界,R可以用下标i来表示,那么左边界L为i-k+1当队首元素的下标小于滑动窗口左边界L时表示队首元素已经不再滑动窗口内

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        deque<int> q;                                                //双端队列
        for (int i = 0; i < k; i++) {                                //将数组的前k个元素放入单调队列中
            while (!q.empty() && nums[i] >= nums[q.back()]) {        //如果遍历到的元素大于等于单调队列队尾元素,则不满足从大到小
                q.pop_back();                                        //那么就将队尾元素移除
            }
            q.push_back(i);                                          //如果队列为空则跳过while循环并将遍历到的元素添加到队尾
        }
        vector<int> result = { nums[q.front()] };                    //窗口大小为k,将队首的元素放进结果数组里
        for (int i = k; i < nums.size(); i++) {                      //滑动窗口从索引为k的元素开始遍历
            while (!q.empty() && nums[i] >= nums[q.back()]) {
                q.pop_back();
            }
            q.push_back(i);
            while (q.front() <= i - k) {                             //判断队首的元素是否在滑动窗口范围内  等同于q.front() <= i - k + 1
                q.pop_front();
            }
            result.push_back(nums[q.front()]);
        }
        return result;
    }
};

评论区大佬更巧妙的代码:

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        deque<int>q; //双端队列
        vector<int>res;
        for (int i = 0; i < nums.size(); i++) {
            while (q.size() && i - k + 1 > q.front())  q.pop_front(); //判断队头是否在滑动窗口范围内
            while (q.size() && nums[i] >= nums[q.back()]) q.pop_back();//维护单调递减队列
            q.push_back(i); //将当前元素插入队尾
            if (i >= k - 1)  res.push_back(nums[q.front()]); //滑动窗口的元素达到了k个,才可以将其加入答案数组中
        }
        return res;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值