Tutorial: Lazy Deletion with Priority Queue (C++ & Python)
Hi everyone! I'm Eman, and this is my very first blog on Codeforces. Today, I'd like to share a simple tutorial on Lazy Deletion using a Priority Queue.
The Problem: Why do we need this?
Normally, you cannot delete an element from the middle of a priority queue. You can only delete the top element. However, in many competitive programming problems, we need to add elements, get the maximum (or minimum) element, and delete specific elements quickly.
The Idea: Lazy Deletion
Instead of looking for the element to delete it right away, we delay the deletion! We do not point to the element to delete it. Instead, we record in another place that it is deleted. When this element reaches the top of the priority queue, only then we delete it.
One way to do this is to create two priority queues:
A main priority queue for the elements, called
pq.A second priority queue to put the elements you want to delete in, called
deleted_pq.
How to handle the operations:
When you add an element: you push it into
pq.When you want to delete an element: you push it into
deleted_pq.When you want to get the largest element in the main pq: you use a while loop to compare the top of
pqwith the top ofdeleted_pq. If they are equal, this means the element is deleted, so you pop from both of them. We repeat this until the top element in the main queue is not a deleted element.
C++ Code
Here is the C++ implementation. It is very useful if you want to avoid the slow constant factor of std::multiset when you only need to add, delete, and get the maximum element.
#include <iostream>
#include <queue>
using namespace std;
priority_queue<int> pq;
priority_queue<int> deleted_pq;
// Function to clean the top from deleted elements
void clean_top() {
// While both tops are equal, the current element is deleted
while (!pq.empty() && !deleted_pq.empty() && pq.top() == deleted_pq.top()) {
pq.pop();
deleted_pq.pop();
}
}
void add_element(int x) {
pq.push(x);
}
void remove_element(int x) {
deleted_pq.push(x);
}
int get_max() {
clean_top(); // Clean the top first
if (!pq.empty()) {
return pq.top();
}
return -1; // If the queue is empty
}
Why is this important for Python?
This trick is a lifesaver for Python users in Competitive Programming. Python does not have a built-in multiset. If you use list.remove() to delete an element, it takes $$$O(N)$$$ time, which often leads to Time Limit Exceeded (TLE).
By using Lazy Deletion with the heapq library, you can add, remove, and get the maximum/minimum in just $$$O(\log N)$$$ time.
Important Note: The heapq library in Python is a Min-Heap by default. To make it work as a Max-Heap (like the C++ example), we multiply the elements by -1 when adding them, and multiply by -1 again when we get them.
import heapq
pq = []
deleted_pq = []
def clean_top():
# Compare the smallest element (which is the largest absolute value)
while pq and deleted_pq and pq[0] == deleted_pq[0]:
heapq.heappop(pq)
heapq.heappop(deleted_pq)
def add_element(x):
# Multiply by -1 to simulate a Max-Heap
heapq.heappush(pq, -x)
def remove_element(x):
# Record the element in the deleted list the same way
heapq.heappush(deleted_pq, -x)
def get_max():
clean_top() # Make sure the current top is not deleted
if pq:
return -pq[0] # Multiply by -1 again to get the original value
return -1
Time Complexity Summary:
- Add: $$$O(\log N)$$$
- Lazy Remove: $$$O(\log N)$$$
- Get Max: $$$O(\log N)$$$ (Amortized)
I hope this tutorial is clear and helpful.








Auto comment: topic has been updated by Eman_sh (previous revision, new revision, compare).
why not just use a set?
A set can't replace this in general because it only stores unique values. A multiset can, but the author already mentioned that priority_queue with lazy deletion often has better constant factors than multiset.
why dont you just mark it as deleted with a bool instead of using another pq
The reason we don't use a simple boolean array (like
is_deleted[x] = true;) is because of duplicate elements.If all elements are guaranteed to be unique, a boolean array would work perfectly. However, if we push the number 5 into the priority queue twice, and we only want to remove one of them, setting
is_deleted[5] = true;would accidentally mark both of them as deleted.Using a Map
To handle duplicates without a second priority queue, we can use a std::map to keep track of the frequencies of the deleted elements.
Memory Optimization Note: A naive approach would use
deleted_counts[pq.top()] > 0to check for deleted elements. However, this accidentally inserts the element into the map with a value of 0 if it doesn't already exist, which wastes memory over time.To optimize memory usage, we should use
map::find()to check if the element exists without inserting it. Furthermore, once the frequency of a deleted element reaches 0, we should remove it completely usingerase()to keep our map as small as possible.Both approaches (the second priority queue and the map) are perfectly valid for handling duplicates. Using a second priority queue is sometimes slightly faster in C++ because
std::maphas a higher constant factor.Why not use a set for marking deletion instead of a second PQ? If duplicates are necessary you could use a hashmap instead with the number of elements to delete