优先队列
解题思路:根据题意模拟。用数组存储无限数量的栈。重在实现 p u s h push push 和 p o p pop pop 操作。
- 对于
p
u
s
h
push
push 操作,需要知道当前从左往右第一个空栈的下标。分两类讨论:
①所有栈都是满的,那么我们创建一个新栈,新栈存 p u s h push push 进来的值,再将新栈加入数组尾部。
②已存在的某些栈未满,那么从左往右遍历数组,找到第一个未满的栈,第一个未满的栈存 p u s h push push 进来的值。
这样一来,我们发现每个 p u s h push push 的操作②的最坏平均时间复杂度 O ( n ) O(n) O(n) ,一共 n n n 个 p u s h push push ,总体时间复杂度 O ( n 2 ) O(n^2) O(n2),题目给出 p u s h push push 操作最多调用 2 × 1 0 5 2 \times 10 ^ 5 2×105 次,时间 O ( n 2 ) O(n^2) O(n2) 必然超时。
操作②既然是遍历,有一个直观的优化方法:用优先队列,小根堆存储未满栈的下标。这样以来,维护未满的栈的时间复杂度就是 O ( l o g n ) O(logn) O(logn),每次维护完毕,堆顶就是最小下标。总体时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn),可以接受。
-
对于 p o p pop pop 操作,它等价于 p o p A t S t a c k popAtStack popAtStack 操作,将参数 i n d e x index index 设为数组最后位置。
-
对于 p o p A t S t a c k popAtStack popAtStack 操作,简单模拟即可。
提示:对于优先队列的维护,请思考边界。也可以看代码注释。
class DinnerPlates {
public:
int capacity;
vector<stack<int>> S;
priority_queue<int, vector<int>, greater<int>> pq; // 维护未满栈的下标
DinnerPlates(int capacity) {
this->capacity = capacity;
}
void push(int val) {
if (pq.size() && pq.top() >= S.size()) { // 清空越界下标
pq = priority_queue<int, vector<int>, greater<int>>();
}
// while (pq.size() && pq.top() >= S.size()) pq.pop(); // 清空越界下标
if (pq.empty()) { // 插入新栈
stack<int> ts;
ts.push(val);
if (capacity > 1) pq.push(S.size());
S.push_back(ts);
} else { // 插入第一个未满栈
int pos = pq.top();
S[pos].push(val);
if (S[pos].size() >= capacity) { // 插入后,栈满
pq.pop(); // 下标弹栈
}
}
}
int pop() {
return popAtStack(S.size() - 1);
}
int popAtStack(int index) {
if (index >= S.size() || S[index].empty()) {
return -1;
} else {
int ans = S[index].top();
S[index].pop();
if (S[index].size() == capacity - 1) pq.push(index);
while (S.size() && S.back().empty()) S.pop_back();
return ans;
}
}
};
时间复杂度
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn) : 一共
n
n
n 次操作,每个操作的时间复杂度
O
(
l
o
g
n
)
O(logn)
O(logn) ,时间瓶颈在于维护堆。总体时间复杂度
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
空间复杂度
O
(
n
)
O(n)
O(n) : 优先队列、栈的最坏空间复杂度
O
(
n
)
O(n)
O(n)
AC
致语
- 理解思路很重要
- 读者有问题请留言,清墨看到就会回复的。