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 <= tokens.length <= 104
tokens[i]
是一个算符("+"
、"-"
、"*"
或"/"
),或是在范围[-200, 200]
内的一个整数
题解
栈的经典题目 ✨
由于逆波兰表达式的特点,每个运算符总是要处理其前面的两个操作数。因此,可以采用栈存储表达式中的操作数,当遇到运算符时就弹出栈顶两个元素、进行运算,并把结果入栈。最后,这般处理完整个表达式,栈中剩余的唯一元素就是计算结果。
代码(C++)
int evalRPN(vector<string> &tokens)
{
stack<int> numSt; // 存储数字的栈
for (const string &token : tokens) {
if (token == "+") {
int num1 = numSt.top();
numSt.pop();
int num2 = numSt.top();
numSt.pop();
numSt.push(num1 + num2);
} else if (token == "-") {
int num1 = numSt.top();
numSt.pop();
int num2 = numSt.top();
numSt.pop();
numSt.push(num2 - num1);
} else if (token == "*") {
int num1 = numSt.top();
numSt.pop();
int num2 = numSt.top();
numSt.pop();
numSt.push(num1 * num2);
} else if (token == "/") {
int num1 = numSt.top();
numSt.pop();
int num2 = numSt.top();
numSt.pop();
numSt.push(num2 / num1);
} else
numSt.push(stoi(token)); // 数字无脑入栈即可
}
return numSt.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
题解
LeetCode标记 困难 \textcolor{red}{\text{困难}} 困难 题目
题目不难理解,可以先无脑写出暴力解法:
vector<int> maxSlidingWindowViolence(vector<int> &nums, int k)
{
vector<int> res;
int left = 0, right = k - 1;
while (right < nums.size())
{
int maxNum = nums[left];
for (int i = left + 1; i <= right; i++)
maxNum = max(maxNum, nums[i]);
res.push_back(maxNum);
left++, right++;
}
return res;
}
不出意外地,在数据量大时超时了 🙊
进一步研究不难发现,每次窗口滑动,其实只有最左和最右的值改变了——这容易让人联想到队列数据结构(出队、入队只改变队头、队尾)。如果用队列存储一个窗口中的值,每次只需要
- 将上一次的左边界值出队列
- 将这一次的右边界入队列
但是有一个问题:如何维护窗口最大值?显然,普通的队列无法每次快速找出队中最大值。一个容易联想到的方法是用优先队列(如c++中的 priority_queue
),但是优先队列每次出队的都是最大值,而这个值(很)可能不是左边界值。
更进一步思考,可以发现其实左右边界也不总是需要考虑的——关键在于,维护“这个窗口的最大值”和“可能在下一个窗口中成为最大值的值”,每次取这些数中的最大值加入结果,并尝试把新的右边界值加入其中,这样需要维护的数就变少了不少。
所以针对这题,我们理想的数据结构是一种特殊的队列:
- 按序存储(窗口中的)一部分数据,便于快速取出最大值
- 每次滑动窗口、更新队列时,如果原来的最大值恰是原来的左边界值,需要将其出队
这样的数据结构(特殊的单调队列)没有现成的,需要手动搓出来,之后的代码就简单了:每次尝试出队、入队、取队头(最大值)加入结果即可。
代码
c++
// 用于解题的单调队列,原则:队列中元素单调递减
class MyQueue
{
private:
deque<int> q; // 双端队列
public:
void push(int num)
{
while (!q.empty() && q.back() < num)
q.pop_back();
q.push_back(num);
}
void pop() { q.pop_front(); }
int front() { return q.front(); }
};
// 解题函数
vector<int> maxSlidingWindow(vector<int> &nums, int k) {
vector<int> res;
MyQueue q;
for (int i = 0; i < k; ++i)
q.push(nums[i]);
res.push_back(q.front());
int left = 1, right = k;
while (right < nums.size()) {
if (nums[left - 1] == q.front())
q.pop();
q.push(nums[right]);
res.push_back(q.front());
left++, right++;
}
return res;
}
go
type MyQueue struct {
q []int
}
func (mq MyQueue) front() int {
return mq.q[0]
}
func (mq MyQueue) back() int {
return mq.q[len(mq.q) - 1]
}
func (mq *MyQueue) pop() {
mq.q = mq.q[:len(mq.q)-1]
}
func (mq *MyQueue) push(num int) {
for len(mq.q) != 0 && mq.back() < num {
mq.pop()
}
mq.q = append(mq.q, num)
}
func (mq MyQueue) size() int {
return len(mq.q)
}
func maxSlidingWindow(nums []int, k int) []int {
res := []int{}
mq := MyQueue{}
left, right := 0, k-1
for right < len(nums) {
if mq.size() > 0 && nums[left] == mq.front() {
mq.pop()
}
mq.push(nums[right])
res = append(res, mq.front())
left++
right++
}
return res
}
该思路更详细的讲解参见 代码随想录——滑动窗口最大值 。
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
个高频元素的集合是唯一的
进阶: 你所设计算法的时间复杂度 必须 优于 O(n log n)
,其中 n
是数组大小。
题解
将问题拆解,依次解决:
1️⃣ 记录数组中元素出现的频率:容易想到用哈希表实现,key:元素;value:出现次数。
2️⃣ 统计高频词汇:找到哈希表中,value最高的k个key,也就是要对整个哈希表按照value排序。比较方便的方法就是采用优先队列,将所有键值对按照value大小加入其中,队列最前面的k个元素即为所求。
代码(C++)
vector<int> topKFrequent(vector<int> &nums, int k)
{
vector<int> res;
auto cmp = [](pair<int, int> a, pair<int, int> b) { return a.second < b.second; };
priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(cmp)> pq(cmp);
unordered_map<int, int> freqMap;
for (int num : nums)
freqMap[num]++;
for (auto pair : freqMap)
pq.push(pair);
for (int i = 0; i < k; i++) {
res.push_back(pq.top().first);
pq.pop();
}
return res;
}