一、题目描述
请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。若队列为空,pop_front 和 max_value 需要返回 -1
示例1:
输入:
["MaxQueue","push_back","push_back","max_value","pop_front","max_value"]
[[],[1],[2],[],[],[]]
输出: [null,null,null,2,1,2]
示例2:
输入:
["MaxQueue","pop_front","max_value"]
[[],[],[]]
输出: [null,-1,-1]
限制:
1 <= push_back、pop_front、max_value的总操作数 <= 10000
1 <= value <= 105
二、思路分析
注:思路分析中的一些内容和图片参考自力扣各位前辈的题解,感谢他们的无私奉献
思路
对于普通队列,入队 p u s h _ b a c k ( ) push\_back() push_back() 和出队 p o p _ f r o n t ( ) pop\_front() pop_front() 的时间复杂度均为 O ( 1 ) O(1) O(1)。本题难点为实现查找最大值 m a x _ v a l u e ( ) max\_value() max_value() 的 O ( 1 ) O(1) O(1) 时间复杂度。假设队列中存储 N N N 个元素,从中获取最大值需要遍历队列,时间复杂度为 O ( N ) O(N) O(N),单从算法上无优化空间。
最直观的想法是维护一个最大值变量 ,在元素入队时更新此变量即可。
但当最大值出队后,并无法确定下一个次最大值 ,因此不可行。
可以考虑利用数据结构来实现,即经常使用的空间换时间。如下图所示,考虑构建一个递减列表来保存队列所有递减的元素,递减链表随着入队和出队操作实时更新,这样队列最大元素就始终对应递减列表的首元素,实现了获取最大值 O ( 1 ) O(1) O(1) 时间复杂度。
为了实现此递减列表,需要使用双向队列,假设队列已经有若干元素:
①当执行入队 p u s h _ b a c k ( ) push\_back() push_back() 时:若入队一个比队列某些元素更大的数字 x x x,则为了保持此列表递减,需要将双向队列尾部所有小于 x x x 的元素弹出
②当执行出队 p o p _ f r o n t ( ) pop\_front() pop_front() 时:若出队的元素是最大元素,则双向队列需要同时将首元素出队,以保持队列和双向队列的元素一致性。
使用双向队列原因:维护递减列表需要元素队首弹出、队尾插入、队尾弹出操作皆为 O ( 1 ) O(1) O(1) 时间复杂度。
函数设计:
初始化队列queue
,双向队列deque
①最大值max_value()
----当双向队列deque
为空,则返回−1
----否则,返回deque
首元素
②入队push_back()
----将元素value
入队queue
----将双向队列中队尾所有小于value
的元素弹出(以保持deque
非单调递减),并将元素value
入队deque
③出队pop_front()
----若队列queue
为空,则直接返回−1
----否则,将queue
首元素出队
----若deque
首元素和queue
首元素相等,则将deque
首元素出队(以保持两队列元素一致)
设计双向队列为单调不增的原因:若队列queue
中存在两个值相同的最大元素,此时queue
和deque
同时弹出一个最大元素,而queue
中还有一个此最大元素,即采用单调递减将导致两队列中的元素不一致。
案例分析:
复杂度分析:
时间复杂度 O ( 1 ) \rm{O(1)} O(1):max_value()
、push_back()
的时间复杂度均为O(1)
。对于push_back()
,假设是543216,只有最后一次push_back
操作是O(N)
,其他每次操作的时间复杂度都是O(1)
,均摊时间复杂度为 ( O ( 1 ) × ( n − 1 ) + O ( n ) ) / n = O ( 1 ) (\mathcal{O}(1)\times (n-1)+\mathcal{O}(n))/n=\mathcal{O}(1) (O(1)×(n−1)+O(n))/n=O(1)。
空间复杂度 O ( N ) \rm{O(N)} O(N):当元素个数为N
时,最差情况下deque
中保存N
个元素,使用O(N)
的额外空间;
三、整体代码
整体代码如下
typedef struct
{
int sq[10000]; // 使用数组保存队列 (正常队列)
int max[10000]; // 用于保存最大值队列 (反向队列)大值在前,小值在后,这么做主要是为了好操作
int back, front; // 保存元素队列的头尾指针
int size; // 保存对中的元素个数
int maxback, maxfront; // 最大值队列头尾指针
int maxsize; // 最值队列个数
} MaxQueue;
// 队列的特点:先进先出
MaxQueue *maxQueueCreate()
{
MaxQueue *queue = malloc(sizeof(MaxQueue));
if (queue == NULL)
return NULL;
queue->maxback = -1;
queue->maxfront = -1;
queue->back = -1;
queue->front = -1;
queue->size = 0;
queue->maxsize = 0;
return queue;
}
int maxQueueMax_value(MaxQueue *obj)
{
if (obj->maxsize <= 0)
return -1;
return obj->max[obj->maxfront];
}
void maxQueuePush_back(MaxQueue *obj, int value)
{
if (obj->back >= 10000) // 队列满,入队失败
return;
obj->sq[++obj->back] = value;
obj->size++;
// 处理队头
if (obj->front == -1)
{
obj->front++;
}
// 处理最值
if (obj->maxfront == -1)
{
obj->maxfront = 0;
obj->maxback = 0;
obj->max[obj->maxfront] = value;
obj->maxsize++;
}
else if (value > obj->max[obj->maxback])
{ // 大于max队尾,将小于该值的队尾元素出队,然后将该元素放入队尾
// 这里比较难理解,可以结合画图,做几个元素的出入队即可
while (obj->maxsize != 0 && value > obj->max[obj->maxback])
{
obj->maxback--;
obj->maxsize--;
}
obj->max[++obj->maxback] = value;
obj->maxsize++;
}
else
{// 小于max队尾,直接放入max队尾
obj->max[++obj->maxback] = value;
obj->maxsize++;
}
}
int maxQueuePop_front(MaxQueue *obj)
{
if (obj->front > obj->back || obj->front == -1)
{// 空队列直接返回空
return -1;
}
if (obj->sq[obj->front] == obj->max[obj->maxfront])
{// 判断max是否出队
obj->maxfront++;
obj->maxsize--;
}
int res = obj->sq[obj->front];
obj->front++;
obj->size--;
return res; // 返回出队元素
}
void maxQueueFree(MaxQueue *obj)
{
free(obj);
}
运行,测试通过