剑指offer 打卡计划 | 每日进步一点点 | 第十五天

91 阅读3分钟

图片.png

一起养成写作习惯!这是我参与「掘金日新计划 · 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. 数据流中的中位数

知识补充:

www.cnblogs.com/wangchaowei…

cloud.tencent.com/developer/a…

数据结构 - 堆

  • Heap是一种数据结构具有以下的特点: (1)完全二叉树; (2)heap中存储的值是偏序
  • Min-heap: 父节点的值小于或等于子节点的值;
  • Max-heap: 父节点的值大于或等于子节点的值;

图片.png

优先队列:

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、当数据个数是奇数的时候,中位数就是大根堆堆顶,是偶数的时候就是大根堆与小根堆堆顶的平均值。

图片.png

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]

图示

图片.png

时间复杂度分析: 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;
    }
};