150. 逆波兰表达式求值
本题不难,但第一次做的话,会很难想到,所以先看视频,了解思路再去做题 。题目链接/文章讲解/视频讲解
还要掌握字符串转换函数C++字符串转换。
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<long long> st; // 临时存储运算的栈
for (auto a : tokens) {
if (a == "+" || a == "-" || a == "*" || a == "/") {
// 取两个元素
long long num1 = st.top();
st.pop();
long long num2 = st.top();
st.pop();
// 运算,注意num1和2的运算顺序
if (a == "+")
st.push(num2 + num1);
if (a == "-")
st.push(num2 - num1);
if (a == "*")
st.push(num2 * num1);
if (a == "/")
st.push(num2 / num1);
} else { // 遇到数压入
st.push(stoll(a));
}
}
int val = st.top();
st.pop();
return val;
}
};
239. 滑动窗口最大值(队列的应用)
(有点难度,可能代码写不出来,一刷至少需要理解思路) 题目链接/文章讲解/视频讲解
卡哥思路:每次窗口移动的时候,调用que.pop(滑动窗口中移除元素的数值),que.push(滑动窗口添加元素的数值),然后que.front()就返回我们要的最大值。所以要把这三个函数写好,然后滑动窗口即可。
ps:感觉这个思路好理解,,但是写代码的时候就不知道从何下手。
class Solution {
public:
deque<int> que;
// 滑动窗口向后移动,前面元素pop
void pop(int x) {
// 如果pop的元素是出口处的元素
// 说明左边要遗弃的元素是之前滑动窗口的最大值
if (!que.empty() && que.front() == x) {
que.pop_front();
}
// 因为其它情况在push的时候已经把前面的元素剔除了
}
// 为什么从back开始 不是front
void push(int x) {
// 比你队列后面的元素都大,则这些元素都弹出
while (!que.empty() && que.back() < x) {
que.pop_back();
}
que.push_back(x);
}
// 因为窗口出口处维护的是最大值,所以直接返回
int front() {
return que.front();
}
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
Solution que;//用类定义
vector<int> result;
for (int i = 0; i < k; i++) { // 先将前k的元素放进队列
que.push(nums[i]);
}
result.push_back(que.front()); // result 记录前k的元素的最大值
for (int i = k; i < nums.size(); i++) {
que.pop(nums[i - k]); // 滑动窗口移除最前面元素
que.push(nums[i]); // 滑动窗口前加入最后面的元素
result.push_back(que.front()); // 记录对应的最大值
}
return result;
}
};
思路2:自己写了一个根据chatgpt修改(感觉有点绕)。
双端队列(Deque):这里使用一个双端队列来存储数组索引,而不是元素值。这种方法可以快速获取当前窗口的最大值,并且可以快速移除不在窗口内的索引。
维护队列:队列的前端始终包含当前窗口的最大值的索引。当一个新元素比队列中已有元素大时,队列后端较小的元素会被移除,因为它们不再可能是任何未来窗口的最大值。
性能:这种方法的时间复杂度为 O(n),因为每个元素最多被推入和弹出队列一次。
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
vector<int> res;
deque<int> dq; // 双端队列,存储索引
for (int i = 0; i < nums.size(); i++) {
// 移除队列中不在滑动窗口内的元素(它们的索引小于当前索引 - k + 1)
if (!dq.empty() && dq.front() < i - k + 1) {
dq.pop_front();
}
// 移除队列中所有小于当前元素 nums[i]的元素,因为它们不可能是当前或后续窗口的最大值
while (!dq.empty() && nums[dq.back()] < nums[i]) {
dq.pop_back();
}
// 将当前元素的索引添加到队列末尾
dq.push_back(i);
// 从第 k 个元素开始,记录每个窗口的最大值
if (i >= k - 1) {
res.push_back(nums[dq.front()]);
}
}
return res;
}
};
问题: 1、deque和queue区别
347.前 K 个高频元素(大/小顶堆的应用)
(有点难度,可能代码写不出来,一刷至少需要理解思路)大/小顶堆的应用, 在C++中就是优先级队列
本题是 大数据中取前k值 的经典思路。题目链接/文章讲解/视频讲解
前言: 如果用排序的话:
都不满足题目复杂度要求,那么借助哈希表。
统计元素出现的频率用map,但map本身是对key(存储元素)排序的,本题需要val(存储元素出现的次数)排序,所以复杂度是nlogn。那么考虑用优先级队列。优先级队列就是一个披着队列外衣的堆,因为优先级队列对外接口只是从队头取元素,从队尾添加元素,再无其他取元素的方式,看起来就是一个队列。而且优先级队列内部元素是自动依照元素的权值排列。那怎么排序:缺省情况下priority_queue利用max-heap(大顶堆)完成对元素的排序,这个大顶堆是以vector为表现形式的complete binary tree(完全二叉树)。
思路: 大顶堆就是二叉树中根节点最大的元素,小顶堆就是二叉树中根节点最小的元素。因为大顶堆顶部是最大的,而遍历的时候Push进来一个元素就要pop一个元素(为什么push了之后还得pop?),而pop栈顶就把最大的弹出了,那最后留下的就是前k个低频元素了,不是高频,所以要用小顶堆。而因为维护k个所以复杂度是nlogk,优于nlogk。
1、如何定义小顶堆:
priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;
在 C++ 中使用 std::priority_queue 与 std::pair<int, int> 结合是一种常见的技术,尤其是当你需要维护一组具有优先级的元素时。std::pair 允许你将两个可能不同类型的值捆绑在一起,通常在这种情况下,pair 的第一个元素用作优先级(如权重或成本),第二个元素用作携带的数据(如节点标识符或其他有用的信息)。
这行代码定义了一个优先队列,其中:
元素类型:std::pair<int, int>,这里每个 pair 的第一个 int 可能代表某种优先级(例如成本、频率等),第二个 int 可能是与该优先级相关的额外数据(例如某种标识符)。
容器类型:std::vector<pair<int, int>>,这是实际存储队列元素的底层容器。虽然 std::priority_queue 默认使用 std::vector 作为其底层容器,这里显式声明了用于清晰。
比较器类型:mycomparison,这是一个函数对象或者函数指针,用于定义优先队列中元素的优先级顺序。比较器的功能是决定哪些元素应该先于其他元素出队。
2、比较器的实现
class mycomparison {
public:
bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
return lhs.second > rhs.second;// 更小的数字表示更高的优先级
}
};
按照 pair 的 second 值的升序排列。也就是说,队列的顶部(优先出队的位置)将会是 second 值最小的元素。
3、使用迭代器遍历map
在 C++ 中,unordered_map<int, int>::iterator 是一种迭代器类型,用于遍历和访问 std::unordered_map<int, int> 容器中的元素。下面是一个如何使用 unordered_map<int, int>::iterator 的例子:
#include <iostream>
#include <unordered_map>
int main() {
std::unordered_map<int, int> umap;
umap[1] = 100;
umap[2] = 200;
umap[3] = 300;
// 使用迭代器遍历unordered_map
for (std::unordered_map<int, int>::iterator it = umap.begin(); it != umap.end(); ++it) {
std::cout << "Key: " << it->first << ", Value: " << it->second << std::endl;
// 修改值
it->second += 100;
}
// 再次打印修改后的值
std::cout << "Modified values:" << std::endl;
for (auto& pair : umap) {
std::cout << "Key: " << pair.first << ", Value: " << pair.second << std::endl;
}
return 0;
}
迭代器提供对元素的直接访问,其中 it->first 是键,it->second 是值。
最终代码:
class Solution {
public:
// 定义一个比较器,优先级高的先出队(小顶堆)
// 使其按照 pair 的 second
// 值的升序排列。也就是说,队列的顶部(优先出队的位置)将会是 second
// 值最小的元素
struct mycomparison {
bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
return lhs.second > rhs.second; // 更小的数字表示更高的优先级
}
};
vector<int> topKFrequent(vector<int>& nums, int k) {
// 统计元素出现的频率
unordered_map<int, int> map;
for (int i = 0; i < nums.size(); i++) {
map[nums[i]]++;
}
// 对频率<key,val>排序
// 定义一个小顶堆,大小为k
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) { // 如果堆的大小大于了K,则队列弹出,保证堆的大小一直为k
pri_que.pop();
}
}
// 如果按照高频到低频的顺序输出
// 找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒序来输出到数组
vector<int> result(k);
for (int i = k - 1; i >= 0; i--) {
result[i] = pri_que.top().first; //first是元素
pri_que.pop();
}
return result;
}
};
**问题:**其中对于比较器定义不一样。在 C++ 中,struct 和 class 关键字都可以用来定义类。它们之间的主要区别在于默认的访问权限和继承权限:
默认访问权限:
在 struct 中,默认成员和继承的访问权限是 public。
在 class 中,默认成员和继承的访问权限是 private。
这意味着,除非你明确指定,否则 struct 成员和基类都是公开的,而 class 的成员和基类则默认为私有。
总结
学习时间
2024.6.17