一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第17天,点击查看活动详情。
剑指 Offer 40. 最小的k个数
思路
(堆排序) O(nlogk)
维护一个大小为k的大根堆,将数组元素都push进堆,当堆中的数大于k时弹出堆顶元素。注意弹出堆顶的顺序是从大到小的k个数,要进行逆序操作。
时间复杂度分析: 建堆的时间复杂度是O(logk),要进行n次建堆的操作。
c++代码
class Solution {
public:
vector<int> getLeastNumbers(vector<int>& arr, int k) {
priority_queue<int> heap; //大根堆,根节点是最大的元素
for(int x : arr){
heap.push(x);
if(heap.size() > k) heap.pop();
}
vector<int> res;
while(heap.size()){
res.push_back(heap.top());
heap.pop();
}a
reverse(res.begin(), res.end());
return res;
}
};
剑指 Offer 41. 数据流中的中位数
知识补充:
cloud.tencent.com/developer/a…
数据结构 - 堆
- Heap是一种数据结构具有以下的特点: (1)完全二叉树; (2)heap中存储的值是偏序;
- Min-heap: 父节点的值小于或等于子节点的值;
- Max-heap: 父节点的值大于或等于子节点的值;
优先队列:
priority_queue称为“优先队列”,其底层是用堆实现。在优先队列中,队首元素一定是当前队列中优先级最高的哪一个。
默认的定义优先队列是大根堆,即父节点的值大于子节点的值。
获取堆顶元素
top():可以获得队首元素(堆顶元素),时间复杂度为O(1)。 与队列不一样的是,优先队列通过top()函数来访问队首元素(堆顶元素)。(队列是通过front()函数和back()函数访问下标)
入队
push(x) :令x入队,时间复杂度为O(logN),其中N为当前优先队列中的元素个数。
出队
pop(): 令队首元素(堆顶元素)出队,时间复杂度为O(logN),其中N为当前优先队列中的元素个数。
检测是否为空
empty():检测优先队列是否为空,返回true为空,false为非空。时间复杂度为O(1)
获取元素个数
size():用来获得优先队列中元素的个数,时间复杂度为 O(1)
案例代码
#include
#include
using namespace std;
int main(){
priority_queue q;
//入队
q.push(3);
q.push(4);
q.push(1);
//通过下标访问元素
printf("%d\n",q.top());//输出4
//出队
q.pop();
printf("%d\n",q.top());//输出3
//检测队列是否为空
if(q.empty() == true) {
printf("Empty\n");
} else {
printf("Not Empty\n");
}
//获取长度
//printf("%d\n",q.size());//输出3
}
大根堆就是根节点是整棵树的最大值,小根堆就是根节点是整棵树的最小值。
基本数据类型的优先级设置
一般情况下,数字大的优先级更高。(char类型的为字典序最大) 对于基本结构的优先级设置。下面两种优先队列的定义是等价的:
priority_queue<int> q; // 大根堆
priority_queue<int,vector<int>,less<int> > q;
如果想让优先队列总是把最小的元素放在队首,需进行以下定义:
priority_queue<int, vector<int>, greater<int>> q //小根堆
思路
(双顶堆) O(logn)
我们可以使用两个堆来解决这个问题,使用一个大顶堆保存整个数据流中较小的那一半元素,再使用一个小顶堆保存整个数据流中较大的那一半元素,同时大顶堆堆顶元素 <= 小顶堆堆顶的元素。
具体过程:
- 1、建立一个大根堆,一个小根堆。大根堆存储小于当前中位数,小根堆存储大于等于当前中位数。
- 2、执行
addNum操作时,如果新加入的元素num小于等于大顶堆down堆顶元素,则加入大根堆中,否则加入小根堆中。 - 3、为了维持左右两边数的数量,我们可以让大根堆的大小最多比小根堆大
1,每次插入后,可能会导致数量不平衡,所以如果插入后哪边的元素过多了,我们将该堆的堆顶元素弹出插入到另一个堆中。 - 4、当数据个数是奇数的时候,中位数就是大根堆堆顶,是偶数的时候就是大根堆与小根堆堆顶的平均值。
c++代码
class MedianFinder {
public:
priority_queue<int, vector<int>, greater<int>> up; //小根堆
priority_queue<int> down; // 大根堆
/** initialize your data structure here. */
MedianFinder() {
}
void addNum(int num) {
if (down.empty() || num <= down.top()) {
down.push(num);
if (down.size() > up.size() + 1) {
up.push(down.top());
down.pop();
}
} else {
up.push(num);
if (up.size() > down.size()) {
down.push(up.top());
up.pop();
}
}
}
double findMedian() {
if ((down.size() + up.size()) % 2) return down.top();
return (down.top() + up.top()) / 2.0;
}
};
剑指 Offer 42. 连续子数组的最大和
思路
(动态规划) O(n)
状态表示: f[i]表示以num[i]结尾的连续子数组的和的最大值
集合划分: 将集合划分为只有nums[i]一个数,和以nums[i]为结尾的多个数组成的连续子数组两大类
状态计算: f[i] = max(nums[i], f[i - 1] + nums[i] )。
初始化: f[0] = nums[0]。
图示:
时间复杂度分析: O(n)。
c++代码
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int n = nums.size(), res = nums[0];
vector<int> f(n + 1, 0);
f[0] = nums[0];
for(int i = 1; i < n; i++){
f[i] = max(nums[i], f[i - 1] + nums[i]);
res = max(res, f[i]);
}
return res;
}
};