主要思路:
要找每个滑动窗口中最大值,所以单调队列里只需要维护可能成为最大值的数列就行,同时保证为非递增排列
易错点:
(1)pop(value):如果窗口移除的元素value等于单调队列的出口元素,那么队列弹出元素,否则不用任何操作,因为该算法逻辑就是让最大值放在单调队列出口处,而若单调队列出口处与滑动窗口出口处元素相等,说明滑动窗口已经滑动到最大值处了
(2)push(value):如果push的元素value大于入口元素的数值,那么就将队列入口的元素弹出,直到push元素的数值小于等于队列入口元素的数值为止
(3)使用的数据结构为deque
代码实现:
class Solution {
private:
class MyQueue {
public:
deque<int>que;
void myPop(int value) {
//访问que前均要先判断非空
if(!que.empty() && que.front() == value) que.pop_front();
}
//相对于普通的push多了一个判断弹出前面元素的操作
void myPush(int value) {
while(!que.empty() && value > que.back()) que.pop_back();
que.push_back(value);
}
int findMax() {
return que.front();
}
};
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
vector<int>ans;
MyQueue que;
//先处理前k个元素,注意不要忘了返回这前k个元素的最大值
for(int i = 0; i < k; i++) {
que.myPush(nums[i]);
}
ans.push_back(que.findMax());
//滑动窗口是一位一位往后移动的
/*这段代码的逻辑是先弹出滑动窗口中第一个元素,
如果它在单调队列中,由前面函数设计可知其必为最大值,如果不是就不执行;
接着将一个新元素压入单调队列中,即滑动窗口向后移动一位;
最后获取当前单调队列里的最大值*/
for(int i = k; i < nums.size(); i++) {
que.myPop(nums[i - k]);
que.myPush(nums[i]);
ans.push_back(que.findMax());
}
return ans;
}
};
第一次写错误
在获取最开始的前k个元素是忘了获取他们的最大值了
补充
(1)deque
deque是一种双端队列(double-ended queue)数据结构,支持在队列的头部和尾部进行快速的插入和删除操作。定义在头文件deque中
deque是由一段段定长的连续存储空间组成的,每一段存储空间被称为一个缓冲区(buffer),缓冲区之间通过指针进行连接。因此,deque既可以支持在队首和队尾进行插入和删除操作,又可以支持在队列中间进行随机访问,比起其他容器,deque在插入和删除操作上的效率更高。
push_front、push_back、pop_front、pop_back、front、back等,可以用于在deque的头部或尾部插入或删除元素,或者获取deque的头部或尾部元素等
(2)什么时候能用num.size()
C++中,num.size()是一个用于获取容器中元素个数的函数。对于支持size()函数的容器类型,比如vector、string、deque等,可以使用size()函数获取容器中元素的个数。而对于其他类型的变量,如数组或指针,是无法使用size()函数获取其元素个数的。示例如下
- vector<int> num; // 可以使用num.size()
- string str = "Hello, World!"; // 可以使用str.size()
- int arr[5] = {1, 2, 3, 4, 5}; // 无法使用arr.size()
- int* ptr = arr; // 无法使用ptr.size()
(3)class
在C++中,class是一种用于定义对象的数据类型的关键字。它可以包含成员变量和成员函数,用来描述对象的属性和行为。使用class定义的类型,可以像内置类型一样使用,也可以像其他自定义类型一样进行封装和抽象,从而实现更高效、更可靠、更易用的代码。
以下是一个简单的class的例子:
class Person {
public:
// 成员函数
void setName(string name) {
m_name = name;
}
string getName() const {
return m_name;
}
void setAge(int age) {
m_age = age;
}
int getAge() const {
return m_age;
}
private:
// 成员变量
string m_name;
int m_age;
};
在上面的例子中,我们定义了一个名为Person的class,它有两个私有成员变量m_name和m_age,以及四个公有成员函数setName、getName、setAge和getAge。成员变量和成员函数的访问权限是通过public、private、protected等关键字来控制的。public成员可以被任何代码访问和使用,private成员只能被同一个class内部的成员函数访问和使用,protected成员可以被同一个class以及它的派生类的成员函数访问和使用。
可以使用以下语句创建一个Person类型的对象:
Person p;
然后可以使用成员函数来设置和获取对象的属性:
p.setName("Tom");
p.setAge(20);
cout << p.getName() << ", " << p.getAge() << endl;
输出结果为:
Tom, 20
主要思路:
(1)首先遍历原数组,统计元素出现的频率,这一类的问题可以使用map来进行统计。map的键里储存该元素,值里储存该元素出现的次数
(2)再用一个小顶堆存储前k个出现频率最高的元素
(3)倒序将小顶堆中元素存储到结果数组里
易错点:
(1)设计小顶堆
bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
return lhs.second > rhs.second;
}
lhs.second > rhs.second,如果结果不成立,就不交换两者的相对位置;如果成立,就交换。然后代入一个最简单的冒泡排序算法场景,
将 lhs.second
和 rhs.second
进行比较,返回的是 lhs.second > rhs.second
,即如果 lhs.second
大于 rhs.second
,那么 lhs
的优先级就比 rhs
的优先级高,那么优先级高的反而排在后面
代码实现:
class Solution {
//加private声明为这个myComparison的class只能在Solution中使用
private:
// 自定义设计小顶堆
class myComparison {
//加public声明这个myComparison的class中的operator可以在Solution中使用
public:
bool operator()(const pair<int, int>&lhs, const pair<int, int>&rhs) {
return lhs.second > rhs.second;
}
};
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
// 遍历数组,统计次数
unordered_map<int,int>map;
for(int i = 0; i < nums.size(); i++) {
map[nums[i]]++;
}
// 利用优先级队列定义一个大小为k的小顶堆,但这个k要在之后构造时才能体现
priority_queue<pair<int, int>, vector<pair<int, int>>, myComparison> pri_que;
/*利用小顶堆扫描map,虽然遍历map是按键从小到大遍历,但当将其放到pri_que中时是按
小顶堆的方式放入*/
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();
}
}
// 在小顶堆中父节点是最小的,而题目要求返回顺序从大到小,所以先把遍历的元素从后往前排
vector<int> ans(k);
for(int i = k - 1; i >= 0; i--) {
/*pri_que.top() 返回的是一个 pair<int, int> 类型的对象,
而不是一个 int 类型的整数,必须用pri_que.top().first即返回键*/
ans[i] = pri_que.top().first;
pri_que.pop();
}
return ans;
}
};
补充
(1)const pair<int, int>&lhs
加上 &
表示函数将会接收传递进来的参数的地址,而不是参数的值本身。
(2)priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;
定义了一个名为 pri_que
的优先队列,
第一个参数是其中其中存储的元素类型,本题是 pair<int, int>
,也就是由两个 int
值组成的键值对。同时,
第二个参数指明该队列使用的作为底层存储结构的容器,为 vector<pair<int, int>>
第三个参数指明使用的用来定义队列中元素排序方式的比较器类,为自定义的 mycomparison
(3)unordered_map<int, int>::iterator it = map.begin()
初始化一个迭代器 it
,它指向 unordered_map<int, int>
类型的容器 map
的第一个键值对(即 begin()
函数返回的迭代器)。因为 map
是一个关联容器,它的键值对是按照键的大小排序的,因此 it
指向的键值对是键最小的键值对。
(4)pri_que.push(*it);
C++ 中,unordered_map
的迭代器 it
指向一个键值对(即 pair<int, int>
类型),其中 first
表示键,second
表示值。因此,*it
表示解引用这个迭代器,即得到这个键值对。在这个例子中,我们要将键值对放入小顶堆中,因此要使用 *it
。