算法训练营笔记第十三天|239. 滑动窗口最大值、347. 前 K 个高频元素

239. 滑动窗口最大值

单调队列正式登场!| LeetCode:239. 滑动窗口最大值_哔哩哔哩_bilibili

0.队列使用的是dequeue,因为在c++里面,队列底层实验的默认容器是dequeue,是一个双头的队列,每一个口都可以push和pop数据,只要选择是front还是back就可以

1.需要使用的是一个队列,随着每次向右平移一个元素,队列也push一个元素,然后pop一个元素,他们的逻辑是一样的

2.需要在队列中创造一段算法,让它在一个特定的位置(比如队头)放置找到的最大值,这样每次返回队头的数值就可以了

3.当push一个新数据的时候,如果这个数据大于前面的元素,那么就弹出前面的元素,注意这里是pop_back(),因为是从后往前弹出

4.为了让队头的元素是最大值,每次都会弹出前面更小的元素,所以不用像常规的那样一位一位pop,只有当dequeue不为NULL,而且front()=val的时候才会进行这个操作

5.需要创建这个单调增且符合前面我们提到的要求的队列,先在外创建Myqueue

->看了视频两遍还是有些云里雾里,参考老师的答案写出来了代码,这部分以后还是需要重新思考,感觉掌握得不是很扎实

参考写出来的代码:

class Solution {
private:
    class myQ{
        public:
        //创建deque实现单调队列
        deque <int> q;
         
        //首先要判断队列是否为空,因为不能操作空队列
        //判断需要弹出的元素是否等于队列里面的最大值,如果等于需要弹出,因为一般情况下已经被换出去了
        void pop(int val){
           if(!q.empty() && val == q.front()){
               q.pop_front();
           } 
        }

        void push(int val){
            while(!q.empty() && val > q.back()){
               q.pop_back();
           } 
           q.push_back(val);
        }

        //每次需要返回的最大值是在队列的最前端也就是front
        int front(){
            return q.front();
        }
    };
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        //创建一个我们自己定义的单调递增队列
        myQ que;
        //存放每k个子数组的最大值
        vector<int> result;

        //for循环把第一组k个元素放入单调递增数组中
        for(int i = 0; i < k; i++){
            que.push(nums[i]);
        }

        //开始存放后面的元素,每次会pop上一个,push新的
        for(int i = k; i < nums.size(); i++){
            que.pop(nums[i-k]); //上一个
            que.push(nums[i]);
            result.push_back(que.front());
        }

        return result;

    }
};

遇到的问题和debug记录:

1.不记得是怎么创建这个特殊的最大值会放在前面的单调递增数列的类型了,是在class solution里面private: 然后又创建了一个class

2.不知道怎么在maxSlidingWindow里面调用这个数列,以及怎么输出我们想要的结果

        先把前k个元素放进来

        再for循环遍历后面的元素,之后再pop上一个和push下一个

3.输出的东西和需要的不符合

         ->这是因为没有输出第一组元素的最大值 

成功代码:

class Solution {
private:
    class myQ{
        public:
        //创建deque实现单调队列
        deque <int> q;
         
        //首先要判断队列是否为空,因为不能操作空队列
        //判断需要弹出的元素是否等于队列里面的最大值,如果等于需要弹出,因为一般情况下已经被换出去了
        void pop(int val){
           if(!q.empty() && val == q.front()){
               q.pop_front();
           } 
        }

        void push(int val){
            while(!q.empty() && val > q.back()){
               q.pop_back();
           } 
           q.push_back(val);
        }

        //每次需要返回的最大值是在队列的最前端也就是front
        int front(){
            return q.front();
        }
    };
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        //创建一个我们自己定义的单调递增队列
        myQ que;
        //存放每k个子数组的最大值
        vector<int> result;

        //for循环把第一组k个元素放入单调递增数组中
        for(int i = 0; i < k; i++){
            que.push(nums[i]);
        }
        result.push_back(que.front());

        //开始存放后面的元素,每次会pop上一个,push新的
        for(int i = k; i < nums.size(); i++){
            que.pop(nums[i-k]); //上一个
            que.push(nums[i]);
            result.push_back(que.front());
        }

        return result;

    }
};

347.前 K 个高频元素

优先级队列正式登场!大顶堆、小顶堆该怎么用?| LeetCode:347.前 K 个高频元素_哔哩哔哩_bilibili

两个难点:

1.怎么求每个数字出现的频率

2.怎么对频率进行排序

想法递推公式:

1.使用map这种数据结构,map是c++标准库中定义的关联容器,是关键字(key)-值(value)对的结合体。key是出现元素的数值,而value是每个元素出现的次数。接着以value做一个从大到小的排序,但是使用快排要将map转换为vector的结构,然后对整个数组进行排序,快排的时间复杂度太高

---> 因为只需要前k个高频元素,所以能否只关注这前k个值,那需要新的数据结构-大堆顶和小堆顶

2.什么是大堆顶和小堆顶:大堆顶是根节点元素最大的堆或者完全二叉树,以及每个结点的值都大于或等于其左右孩子结点的值;小堆顶是根节点元素最小的堆或者完全二叉树,以及每个结点的值都小于或等于其左右孩子结点的值。

3.使用大堆顶还是小堆顶:首先我们只需要前k个高频元素的值,也就是我们需要维护的,在堆顶里面出现元素的个数是k个,如果使用大堆顶,当你有初始的k个元素之后,再添加元素就会pop出最大的元素,这样就和我们的需求相悖

ps:因为是二叉树的结构,且里面的节点个数是k,所以每添加一个元素,时间复杂度是logk

4.是否存在不需要自行定义结构,已经存在的类似大堆顶或小堆顶的结构:优先队列 priority queue 

5.unordered_map就是不会根据key的大小进行升序排序的map

   map会排序

6.priority_queue< type, container, function>

  • type 类型
  • container:实现优先队列的底层容器(缺省)
  • function:元素之间的比较方式(缺省)

写一遍老师视频里的伪代码加深一下印象:

//遍历数组元素,用map这个数据结构收集对应的key-value

for(int i = 0; i < nums.size(); i++){

        map[nums[i]]++;

}

//创建优先级队列,比较函数是为了构造小堆顶

priority_queue(<key,value>, compare())

//每次都在这个规定数量为k的二叉树里面添加元素,再pop出去

for(map){

        que.push(it)

        if(que.size()>k) que.pop()

}

vector<int> result;

//先pop出来的是最小频率,所以为了让频率正序输出就要反着放进result里面

for(int i = k-1; i>= 0 ; i--){

        result[i] = que.front();

        que.pop();

}

}

原理我懂了,怎么写我懂了,可是在具体的代码实现上还是有很大问题的,因为我是零基础加上对数据结构这方面了解过少,c++语言掌握得也不是非常好,所以我抄了一遍,理解了这个代码,以后再精进对每个语句的理解,慢慢来

class Solution {
//重写仿函数,完成greater的功能,也可以用class定义类,此时需要将运算符重载函数设为public
//小堆顶是greater函数
    
private:
    class compare{
    public:
        bool operator()(const pair<int,int> &lhs, const pair<int,int>&rhs){
            return lhs.second > rhs.second;
        }

    };
    
public:
/*
//结构体struct中默认是访问类型是public
    struct compare{
	    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这个数据结构收集对应的key-value
        for(int i = 0; i < nums.size(); i++){
            map[nums[i]]++;
        }

        //创建优先级队列,里面存储的每个数据的数据类型,整体的数据类型,比较函数(构造小堆顶)
        priority_queue<pair<int,int>, vector<pair<int,int>>,compare> pri_que;
        //每次都在这个规定数量为k的二叉树里面添加元素,再pop出去
        for(unordered_map<int,int> :: iterator it=map.begin(); it!=map.end();it++){pri_que.push(*it);if(pri_que.size()>k) pri_que.pop();
}
vector<int> result(k);

        //先pop出来的是最小频率,所以为了让频率正序输出就要反着放进result里面
        for(int i = k-1; i >= 0 ; i--){
result[i] = pri_que.top().first;
pri_que.pop();
        }
        return result;
    }
};

总结

 这次花了好长时间,得有7h,中间还间隔好久,难度大于我实际有的难度,所以很多东西我没有接触过,每次接触的时候得学相应的知识

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值