创建单调递减队列
class Solution {
private:
class MyQueue { //单调队列(从大到小)
public:
deque<int> que; // 使用deque来实现单调队列
// 每次弹出的时候,比较当前要弹出的数值是否等于队列出口元素的数值,如果相等则弹出。
// 同时pop之前判断队列当前是否为空。
void pop(int value) {
if (!que.empty() && value == que.front()) {
que.pop_front();
}
}
// 如果push的数值大于入口元素的数值,那么就将队列后端的数值弹出,直到push的数值小于等于队列入口元素的数值为止。
// 这样就保持了队列里的数值是单调从大到小的了。
void push(int value) {
while (!que.empty() && value > que.back()) {
que.pop_back();
}
que.push_back(value);
}
// 查询当前队列里的最大值 直接返回队列前端也就是front就可以了。
int front() {
return que.front();
}
};
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
MyQueue 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;
}
};
预备知识
堆Heap:是一种特殊的树形数据结构,通常用于实现优先队列。
堆的主要性质:(1)完全二叉树结构,堆是一个完全二叉树,意味着除了最后一层外,所有 层都是完全填满的,而且最后一层的节点都尽可能地靠左排列。
(2)堆序性质,在堆中,每个节点的值都必须满足堆的性质。
根据堆序性质,大顶堆的根节点是整个堆中的最大值,小顶堆的根节 点是整个堆中的最小值。
堆的常见操作包括:
-
插入(Insert): 将一个新元素插入到堆中。
-
删除(Delete): 从堆中删除根节点,并重新调整堆,以满足堆的性质。
-
查找(Find): 查找堆中的最大值或最小值,取决于是大顶堆还是小顶堆。
-
合并(Merge): 将两个堆合并成一个新的堆。
堆的实现通常使用数组来表示,其中根节点存储在索引 0 处,而其他节点存储在数组的其他位置。通过数组索引之间的关系,可以在堆中轻松地导航并执行堆操作。
堆在算法和数据结构中有广泛的应用,例如优先队列、堆排序、图算法中的最短路径算法(如Dijkstra算法)、中位数查找等。
大顶堆max heap:父节点的值必须大于或等于其子节点的值;
小顶堆min heap:父节点的值必须小于或等于其子节点的值。
优先队列(Priority Queue):是一种特殊的队列,其中每个元素都关联有一个优先级。在优先队列中,元素的出队顺序不仅取决于它们进队的顺序,还取决于它们的优先级。元素的优先级高的先被出队。
优先队列支持以下两种操作:
-
插入(Insert): 向队列中插入一个元素,该元素会按照优先级被正确地放置在队列中。
-
删除最高优先级元素(Delete-Min 或 Delete-Max): 从队列中删除具有最高优先级的元素。
实现优先队列的一种常见方法是使用堆(Heap),特别是二叉堆。在二叉堆中,根节点的元素具有最高的优先级,因此删除最高优先级元素的操作相对较为高效。
优先队列的应用场景包括:
-
任务调度: 在操作系统中,进程的调度可以使用优先队列来确保高优先级的任务先执行。
-
图算法: 在一些图算法中,如Dijkstra算法,Prim算法,优先队列用于选择下一个要访问的节点。
-
模拟系统: 优先队列可以用于模拟系统中的事件调度,确保按照事件的优先级依次处理。
C++标准模板库(STL)提供了一个名为 std::priority_queue
的优先队列实现。默认情况下,它是一个大顶堆,但可以通过自定义比较器来实现小顶堆。以下是一个简单的示例:
#include <iostream>
#include <queue>
int main() {
std::priority_queue<int> maxHeap;
maxHeap.push(3);
maxHeap.push(1);
maxHeap.push(4);
maxHeap.push(1);
maxHeap.push(5);
while (!maxHeap.empty()) {
std::cout << maxHeap.top() << " ";
maxHeap.pop();
}
return 0;
}
预备知识完毕后,本题解题思路:
(1)统计元素出现频率
(2)对频率排序
(3)找出出现频率前k高的元素
class Solution {
public:
// 小顶堆
class mycomparison {
public:
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; // map<nums[i],对应出现的次数>
for (int i = 0; i < nums.size(); i++) {
map[nums[i]]++;
}
// 对频率排序
// 定义一个小顶堆,大小为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;
pri_que.pop();
}
return result;
}
};
重载运算符是指在类中重新定义某些运算符的行为。C++允许程序员对类的成员函数进行重载,以实现对用户定义类型的运算符进行自定义操作。重载运算符使得用户自定义类型的对象可以像基本数据类型一样进行运算。
下面是一些常见的重载运算符以及它们的作用:
1. **算术运算符:** `+`, `-`, `*`, `/`, `%`
```cpp
class Complex {
public:
Complex operator+(const Complex& other) const;
};
```
通过重载加法运算符,使得两个 `Complex` 类型的对象可以使用 `+` 运算符相加。
2. **比较运算符:** `==`, `!=`, `<`, `<=`, `>`, `>=`
```cpp
class Point {
public:
bool operator==(const Point& other) const;
};
```
通过重载相等运算符,可以方便地比较两个 `Point` 类型的对象是否相等。
3. **赋值运算符:** `=`
```cpp
class MyString {
public:
MyString& operator=(const MyString& other);
};
```
通过重载赋值运算符,实现对象间的赋值操作。
4. **下标运算符:** `[]`
```cpp
class Matrix {
public:
int& operator[](int index);
};
```
通过重载下标运算符,使得可以通过类似数组下标的方式访问矩阵的元素。
5. **函数调用运算符:** `()`
```cpp
class Functor {
public:
int operator()(int x, int y) const;
};
```
通过重载函数调用运算符,实现函数对象的可调用行为。
通过重载运算符,可以提供更自然和直观的语法来操作自定义类型的对象,使得用户定义类型能够与内置类型一样方便地使用。需要注意的是,对于一些运算符,如 `<<` 和 `>>` 用于流操作符,也可以进行重载以实现特定的输出和输入行为。
######################################
```cpp
bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
return lhs.second > rhs.second;
}
这部分代码是一个函数调用运算符 `operator()` 的重载,用于自定义比较器。通常,这样的比较器被用于容器类(比如 `std::set` 或 `std::priority_queue`)来定义元素的顺序。
让我们逐个解释这个函数签名:
```cpp
bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
return lhs.second > rhs.second;
}
```
- `bool`:这个函数返回一个布尔值,表示比较结果。返回 `true` 表示 `lhs` 的优先级高于 `rhs`,返回 `false` 表示 `rhs` 的优先级高于等于 `lhs`。
- `operator()`:这是函数调用运算符的名称,表示这是一个函数调用运算符的重载。
- `(const pair<int, int>& lhs, const pair<int, int>& rhs)`:这是函数的参数列表。这个比较器接受两个参数,都是 `std::pair<int, int>` 类型的常引用。
- `const pair<int, int>& lhs` 表示第一个参数,即左操作数,它是一个常引用,避免了对实参进行拷贝。
- `const pair<int, int>& rhs` 表示第二个参数,即右操作数,同样是一个常引用。
- `{ return lhs.second > rhs.second; }`:这是函数体,包含了比较的逻辑。在这个特定的比较器中,它比较了 `lhs` 和 `rhs` 的 `second` 成员(`pair` 类型的第二个元素),如果 `lhs.second` 大于 `rhs.second`,则返回 `true`,否则返回 `false`。
这个比较器通常被用于实现小顶堆,因为返回 `true` 表示 `lhs` 的优先级较低,适用于小顶堆的排序规则。如果希望实现大顶堆,可以调整比较逻辑。
`std::pair<int, int>` 是 C++ 标准库中定义的用于存储两个值的数据结构。这个数据结构可以容纳两个类型为 `int` 的元素,分别称为 `first` 和 `second`。这种结构在很多情况下都非常有用,特别是当需要将两个值作为一个单元进行处理时。
```cpp
#include <iostream>
#include <utility>
int main() {
// 创建一个 pair,存储两个整数
std::pair<int, int> myPair(3, 7);
// 访问 pair 中的第一个和第二个元素
int firstElement = myPair.first;
int secondElement = myPair.second;
// 输出 pair 中的元素
std::cout << "First Element: " << firstElement << std::endl;
std::cout << "Second Element: " << secondElement << std::endl;
return 0;
}
```
在上述示例中,`std::pair<int, int>` 被用于创建一个包含两个整数的 pair。可以通过 `.first` 和 `.second` 成员访问 pair 中的元素。这对于表示键值对、坐标点等情况非常有用。在很多情况下,`std::pair` 可以用于方便地组合两个相关的值。
#############################################################################
for (unordered_map<int,int>::iterator it = map.begin();it != map.end(); it++)
这是一个 `unordered_map` 迭代器的典型用法,用于遍历 `unordered_map` 中的键值对。让我们逐步解释:
```cpp
unordered_map<int, int>::iterator it = map.begin();
```
- `unordered_map<int, int>`:表示 `unordered_map` 的数据结构,这里键和值都是整数。
- `::iterator`:是 `unordered_map` 类型的迭代器,用于遍历 `unordered_map` 中的元素。
- `it`:是迭代器的名称,你可以选择任何有效的标识符来表示迭代器。
- `= map.begin()`:将 `it` 初始化为 `map` 的起始位置(即第一个元素的位置)。
接下来,可以使用这个迭代器来遍历整个 `unordered_map`:
```cpp
for (; it != map.end(); ++it) {
// 这里可以使用 it->first 访问键,it->second 访问值
int key = it->first;
int value = it->second;
// 进行相应的操作
}
```
在这个循环中,`it` 不断地递增,直到达到 `map.end()`,即最后一个元素之后的位置。在循环体内,可以使用 `it->first` 访问键,`it->second` 访问值。这使得你可以遍历 `unordered_map` 中的所有键值对,并对它们进行相应的操作。