https://leetcode-cn.com/problems/dui-lie-de-zui-da-zhi-lcof/
一开始看到操作数和数值都不是很大
我就想用静态链表来维护一个降序的序列,然后想到可能有相同的最大值,我又开了一个数组来维护元素的数量
这个时候类内部的组成是:
操作的逻辑是
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;
}
};
但是效率极低
把队列改成queue,发现效率还是没有提升,说明效率是在更改窗口大小那里
于是使用map来代替cnt
使用map的时候有几点要注意
L=(it==mp.end())?R:it->first;
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;
}
};
看了提交记录里面的答案,果然我还是太菜了
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了非常久
就算是暴力也要思考这个暴力方法可不可行
实践证明,做题目千万不要急着写代码。题目通过时间和写代码前的思考时间成反比。