leetcode 面试题59 - II. 队列的最大值

https://leetcode-cn.com/problems/dui-lie-de-zui-da-zhi-lcof/
leetcode 面试题59 - II. 队列的最大值_第1张图片

一开始看到操作数和数值都不是很大
我就想用静态链表来维护一个降序的序列,然后想到可能有相同的最大值,我又开了一个数组来维护元素的数量
这个时候类内部的组成是:

  1. 一个队列用来完成基本的队列操作
  2. 一个静态链表list维护一个降序的序列,ind指针指向当前的最大值
  3. 一个数组cnt用来维护元素的数量,当由于出队操作导致某个元素个数为0的时候,就修改链表

操作的逻辑是
1. 取最大值,直接返回ind
2. 入队,当入队value时,在list中插入它,并且修改cnt[value]的值
3. 出队,队列出队操作弹出value,然后判断cnt[–value]是否等于0,等于0就在链表中删除value

静态链表写得不是很多,有个地方越界了,自己debug了好久
改好之后提交发现TLE了,猜测应该是出队操作在链表中删除元素时间开销过大,那这个做法就行不通了

TLE的代码

class MaxQueue {
public:
    static const int N=100001;
    int que[N];
    int i,j;    //[i,j)
    int list[N];
    int cnt[N];
    int ind;    //当前队列中的最大值
    MaxQueue() {
        ind=-1;
        i=0,j=0;
        memset(list,-1,N);
        memset(cnt,0,N);
    }
    
    int max_value() {
        return ind;
    }
    
    void push_back(int value) {
        que[j++]=value; //先入队
        // cout<<"push back "<
        // 根据插入值来维护静态链表
        // 如果新值等于最大值 什么都不做    
        if(value==ind) ;
        else if(value>ind){
            // cout<<"updata max "<"<
            // 如果新入队的元素是最大值,直接修改表头
            list[value]=ind;
            ind=value;  //更新最大值
        }
        else{
            // value
            int t=ind;
            // 寻找间隙插入
            while(value<list[t]){
                // ind=list[ind];  // 这里修改了ind!!!是错误的!
                t=list[t];
            }            
            list[value]=list[t];
            list[t]=value;
        }
        cnt[value]++; //该值数量+1
    }
    
    int pop_front() {
        if(ind==-1) return -1;
        int ans=que[i++];
        //该值的数量-1
        cnt[ans]--;
        //如果该值已经不存在了
        if(!cnt[ans]){
            int t=0;
            //如果当前队头就是最大值
            if(ans==ind){
                //指向下一个值
                t=ind;
                ind=list[ind];
                list[t]=-1; //删除原来的表头
            }
            else{
                t=ind;
                while(list[t]!=ans){
                    t=list[t];
                }
                list[t]=list[list[t]];
                list[ans]=-1;
            }

        }
        return ans;
    }
};

手贱点到标签 看到了 滑动窗口,马上get了

吃完饭提交了一下 终于过了

class MaxQueue {
public:
    const static int N=10002;
    int i,j;    //模拟队列[L,R)
    int que[N]; //队列
    int L,R;    //[L,R] 为了方便
    int cnt[100002]; //区间
    MaxQueue() {
        i=j=L=R=0;
        memset(cnt,0,N);
    }
    
    int max_value() {
        return i==j?-1:R;
    }
    
    void push_back(int value) {
        que[j++]=value;
        if(value>R)
            R=value;
        else if(value<L)
            L=value;
        cnt[value]+=1;
    }
    
    int pop_front() {
        if(i==j) return -1;
        int ans=que[i++];
        if(--cnt[ans]) return ans;
        if(ans==L){
            int t=L+1;
            for(;t<R;t++){
                if(cnt[t])
                    break;
            }
            L=t;
        }
        else if(ans==R){
            int t=R-1;
            for(;t>L;t--){
                if(cnt[t])
                    break;
            }
            R=t;
        }
        return ans;
    }
};

但是效率极低
leetcode 面试题59 - II. 队列的最大值_第2张图片
把队列改成queue,发现效率还是没有提升,说明效率是在更改窗口大小那里
于是使用map来代替cnt
使用map的时候有几点要注意

  1. map的迭代器是相邻的
  2. map的开始和结束范围这样的[begin,end)
  3. 尝试更新L的时候,如果超过R,那应该等于R L=(it==mp.end())?R:it->first;
  4. 尝试更新R的时候,如果找到了begin,那应该判断队列中是否还有元素,如果没有元素,那应该等于L,如果有元素,那一定是begin的first原因就是我这里的map不会删除为0的元素
    假如队列长度为0了 map的begin是 126,那么如果不判断长度直接令R=126,再取max就是错的了
    如果队列长度为1,我们可以放心的取得此时begin对应的元素
    R=(it==mp.begin())?(j-i==0?L:mp.begin()->first):it->first;
class MaxQueue {
public:
    const static int N=10002;
    int i,j;    //模拟队列[L,R)
    int que[N]; //队列
    int L,R;    //[L,R] 为了方便
    map<int,int> mp;
    MaxQueue() {
        i=j=L=R=0;

    }
    
    int max_value() {
        // cout<<"max: "<<(i==j?-1:R)<
        return i==j?-1:R;
    }
    
    void push_back(int value) {

        que[j++]=value;
        if(value>R)
            R=value;
        else if(value<L)
            L=value;
        // cout<<"push : "<
        mp[value]++;

    }
    
    int pop_front() {
        if(i==j) return -1;
        int ans=que[i++];
        if((--mp[ans])!=0)return ans;
        //没有个数了
        if(ans==L){
            auto it=mp.find(L);
            while(it!=mp.end()&&!it->second)
                it++;
            L=(it==mp.end())?R:it->first;
        }
        else if(ans==R){
            auto it=mp.find(R);
            while(it!=mp.begin()&&!it->second)
                it--;
            R=(it==mp.begin())?(j-i==0?L:mp.begin()->first):it->first;
        }
        // cout<<"pop : "<
        return ans;
    }
};

效率提高了一点
leetcode 面试题59 - II. 队列的最大值_第3张图片

题解

看了提交记录里面的答案,果然我还是太菜了
128ms的,使用双向队列deque和单向队列queue

class MaxQueue {
public:
    deque<int> ast;
    queue<int> que;
    MaxQueue() {
        ast = deque<int>();
        que = queue<int>();
    }
    
    int max_value() {
        if(que.empty()) return -1;
        return ast.front();
    }
    
    void push_back(int value) {
        que.push(value);
        //从队尾插入value,弹出所有小于value的值
        //之所以能弹出,是因为小于当前的value的值肯定不是最大的,可以放心地弹出,很巧妙的设计
        while(!ast.empty() && ast.back() < value) ast.pop_back();
        ast.push_back(value);
    }
    
    int pop_front() {
        if(que.empty()) return -1;
        int val = que.front();
        que.pop();
        //如果弹出的元素是最大值,那么就更新,如果不是则没必要更新
        if(ast.front() == val) ast.pop_front();
        return val;
    }
};

112ms的,使用链队,分析了一下和我之前的静态链表思路很像。不过我之前的静态链表要检索太多次了,超时了。
注意到nextM指针
这是一个普通的链队加上一个nextM指针,检索nextM指针就能得到降序的序列

struct Node{
    int val;
    Node *next;
    Node *nextM;
    Node(int _val):val(_val),next(NULL),nextM(NULL){}
};
class MaxQueue {
public:
	//首,尾,最大值
    Node* head=NULL;
    Node* tale=NULL;
    Node* max=NULL;
    MaxQueue() {
        
    }
    
    int max_value() {
    	//如果当前没有最大值
        if(max==NULL)return -1;
        else return max->val;
    }
    
    void push_back(int value) {
    	//新建一个节点
        Node *temp=new Node(value);
        if(head==NULL){		//如果头指针为空
            if(tale==NULL){	//如果尾指针也为空
            	//当前新节点同时是首尾和最大值
                head=temp;
                tale=temp;
                max=temp;
            }
        }
        else{	//如果头指针不为空,那么尾指针一定不为空
            tale->next=temp;	//把新节点连接到尾指针后面
            tale=tale->next;	//更新尾指针
            if(value>=max->val){	//如果新的值value大于之前的最大值
                temp->nextM=max;	//新节点的下一个nextM,也就是接下来小于他的第一个数为旧的max
                max=temp;			//max指针指向新的节点
            }
            else{	//value的值小于之前的max
                Node* tempM=max;	//找到新节点合适的插入位置
                while(tempM->nextM!=NULL&&value<tempM->nextM->val)tempM=tempM->nextM;
                temp->nextM=tempM->nextM;	//更新它们的nextM属性
                tempM->nextM=temp;
            }
        }
    }
    
    int pop_front() {
        if(head==NULL)return -1;	//空队 返回-1
        else{
            int res=head->val;	//获得值
            if(head==tale){	//如果是只有一个结点
                max=NULL;
                head=NULL;
                tale=NULL;
            }
            else {
                if(head==max)max=head->nextM;	//如果当前的head是最大值,直接更新head即可
                else{
                    Node* temp=max;		//更新上一个结点的nextM,使其指向当前节点的nextM
                    while(temp->nextM!=head)temp=temp->nextM;
                    temp->nextM=head->nextM;
                }
                head=head->next;
            }
            return res;
        }
    }
};

上面这个写法真的很棒,明天再重新写一下。
现在想来知道了自己当时写的静态链表为什么超时了,首先空间开销就很大,检索时间开销也很大


最后总结一下,今天写这道题花了很长时间
主要一开始没想好怎么做,头脑发热就开始写了,结果写得非常久,debug了非常久
就算是暴力也要思考这个暴力方法可不可行
实践证明,做题目千万不要急着写代码。题目通过时间和写代码前的思考时间成反比。

你可能感兴趣的:(leetcode 面试题59 - II. 队列的最大值)