逆波兰表达式求值
题目描述
根据 逆波兰表示法,求表达式的值。
有效的运算符包括 + , - , * , / 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
说明:
整数除法只保留整数部分。 给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。
示例 1:
- 输入: [“2”, “1”, “+”, “3”, " * "]
- 输出: 9
- 解释: 该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
示例 2:
- 输入: [“4”, “13”, “5”, “/”, “+”]
- 输出: 6
- 解释: 该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6
示例 3:
-
输入: [“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 + 2 ) * ( 3 + 4 ) 。
该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) 。
逆波兰表达式主要有以下两个优点:
- 去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。
- 适合用栈操作运算:遇到数字则入栈;遇到运算符则取出栈顶两个数字进行计算,并将结果压入栈中
解题思路
总体思路
遍历token字符串数组中的所有字符串,如果是数字就将其转换为long long 类型压入栈中,如果是运算符,则取出栈顶的前两个元素进行对应算术运算后,再将结果重新压入到栈中,最后返回栈顶元素即可
时空分析
- 时间复杂度: O(n)
- 空间复杂度: O(n)
代码实现
测试地址:
class Solution {
public:
int evalRPN(vector<string>& tokens) {
// 使用栈来存放整数
stack<long long> st;
// 遍历每个token,token可以是数字或者是运算符
for (string t : tokens) {
// 检查当前token是否是运算符
if (t == "+" || t == "-" || t == "*" || t == "/") {
// 从栈顶取出两个数字进行运算
long long num1 = st.top(); // 第一个数字
st.pop(); // 弹出第一个数字
long long num2 = st.top(); // 第二个数字
st.pop(); // 弹出第二个数字
// 根据运算符执行计算,并将结果压回栈中
if (t == "+") {
st.push(num2 + num1);
} else if (t == "-") {
st.push(num2 - num1);
} else if (t == "*") {
st.push(num2 * num1);
} else if (t == "/") {
st.push(num2 / num1);
}
} else {
// 如果是数字,则将其转换为长整型后压入栈中
st.push(stoll(t));
}
}
// 最后栈顶的元素即为整个表达式的结果
int res = st.top(); // 获取结果
st.pop(); // 弹出结果
return res; // 返回结果
}
};
滑动窗口最大值
题目描述
给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回滑动窗口中的最大值。
进阶:
你能在线性时间复杂度内解决此题吗?
提示:
- 1 <= nums.length <= 10^5
- -10^4 <= nums[i] <= 10^4
- 1 <= k <= nums.length
解题思路
总体思路
可以使用一个双端队列(deque
)来实现高效地找到滑动窗口中的最大值。这个双端队列被设计为一个特殊的队列,即MyQueue
,它在队列的前端存储当前窗口的最大值,并且在队列的后端存储可能成为下一个最大值的元素。
具体步骤如下:
- 初始化窗口:首先,将前
k
个元素加入到MyQueue
中。在加入每个元素时,会从队列的后端开始比较,如果新加入的元素比队列后端的元素大,则将队列后端的元素弹出,直到队列为空或者队列后端的元素比新元素大。这样,队列的前端始终保持当前窗口的最大值。 - 滑动窗口:对于剩余的元素,每次滑动窗口时,先检查队列的前端元素是否与当前窗口的第一个元素相同,如果相同,则从队列前端弹出该元素,因为该元素已经不在当前窗口中了。然后,将新的元素加入到队列中,同样遵循上述规则,确保队列前端始终是最大值。
- 记录最大值:在每次滑动窗口后,队列的前端元素就是当前窗口的最大值,将其加入到结果数组
result
中。 - 返回结果:遍历完所有元素后,返回存储了所有窗口最大值的结果数组。
这种方法的关键在于,MyQueue
队列中始终保持了可能成为最大值的元素,并且这些元素是按照从大到小的顺序排列的。这样,每次只需要检查队列的前端元素,就可以在O(1)时间内得到当前窗口的最大值。同时,由于队列中只存储了可能成为最大值的元素,因此队列的大小通常小于或等于窗口的大小,这使得整个算法的复杂度保持在O(n),其中n是数组的长度。
时空分析
- 时间复杂度: O(n)
- 空间复杂度: O(k)
代码实现
测试地址:https://leetcode.cn/problems/sliding-window-maximum/
class Solution {
private:
// 内部类MyQueue用于在O(1)时间内获取滑动窗口的最大值
class MyQueue {
public:
deque<int> dq; // 双端队列,用于存储当前窗口的最大值及其候选值
// 当窗口滑动时,需要从队列front弹出元素,但仅当该元素等于窗口的前端元素时才弹出
void pop(int value) {
if (!dq.empty() && value == dq.front()) {
dq.pop_front();
}
}
// 向队列中添加元素,若添加的值大于队列尾部元素,则弹出队列尾部元素,以保证队列头部总是最大值
void push(int value) {
while (!dq.empty() && value > dq.back()) {
dq.pop_back();
}
dq.push_back(value);
}
// 获取队列中的头部元素,即为当前窗口的最大值
int front() { return dq.front(); }
};
public:
// 主方法用于计算并返回每个滑动窗口的最大值
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
MyQueue dq; // 实例化内部MyQueue类
vector<int> result; // 用于存储最终结果的数组
// 首先处理前k个元素,初始化滑动窗口
for (int i = 0; i < k; i++) {
dq.push(nums[i]);
}
result.push_back(dq.front()); // 将当前窗口的最大值加入结果
// 遍历数组,继续处理剩余元素
for (int i = k; i < nums.size(); i++) {
dq.pop(nums[i - k]); // 移除滑动窗口最左侧的元素
dq.push(nums[i]); // 添加当前元素到滑动窗口
result.push_back(dq.front()); // 将当前窗口的最大值加入结果
}
return result; // 返回结果数组
}
};
前 K 个高频元素
题目描述
给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
示例 1:
- 输入: nums = [1,1,1,2,2,3], k = 2
- 输出: [1,2]
示例 2:
- 输入: nums = [1], k = 1
- 输出: [1]
提示:
- 你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
- 你的算法的时间复杂度必须优于 O ( n log n ) O(n \log n) O(nlogn) , n 是数组的大小。
- 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的。
- 你可以按任意顺序返回答案。
解题思路
总体思路
- 通过分析题意可以得知,我们需要计算某个数值出现的次数频率,因为涉及到两个数值的存储,所以可以定义哈希表来存储元素以及该元素所出现的频率
- 当得到一张含有数组所有元素以及对应次数的表以后,因为我们需要取出次数出现最多的元素,因此可以考虑采用快速排序的方式,时间复杂度为nlogn,那么有没有效率更高的方法呢?
- 我们只需要取出前k个高频元素,所以可以采用小根堆来维护前k个元素,当小跟堆中的元素大于k的时候,将最小的元素弹出
- 倒序返回小根堆中的元素即可
时空分析
- 时间复杂度: O(nlogk)
- 空间复杂度: O(n)
代码实现
测试地址:https://leetcode.cn/problems/top-k-frequent-elements/description/
class Solution {
public:
// 定义一个比较器,用于创建最小堆
class mycomparison {
public:
// 比较两个pair,根据second值决定优先级
// 如果lhs的second大于rhs的second,则lhs的优先级低
bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
return lhs.second > rhs.second;
}
};
// 主函数,用于找出数组中出现频率最高的前k个元素
vector<int> topKFrequent(vector<int>& nums, int k) {
// 使用哈希表统计每个元素的出现频率
unordered_map<int, int> map;
for (int i = 0; i < nums.size(); i++) {
map[nums[i]]++;
}
// 创建一个最小堆,使用自定义的比较器
// pair<int, int> 元素类型
// vector<pair<int, int> 存储元素的容器
// mycomparison 自定义比较器
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,移除堆顶元素
}
}
// 创建结果数组,用于存储前k个高频元素
vector<int> result(k);
// 从最小堆中提取元素,由于是最小堆,需要反向填充结果数组
for (int i = k - 1; i >= 0; i--) {
result[i] = pri_que.top().first; // 获取元素值
pri_que.pop(); // 从堆中移除元素
}
// 返回结果数组
return result;
}
};