1801. Number of Orders in the Backlog
Problem Description
You are given a 2D integer array orders
representing buy and sell orders in a trading system. Each order orders[i] = [price_i, amount_i, orderType_i]
contains:
price_i
: the price per unitamount_i
: the number of orders at this priceorderType_i
:0
for buy orders,1
for sell orders
The orders are processed sequentially in the given order. A backlog (initially empty) stores unmatched orders.
Order Matching Rules:
When a buy order arrives:
- Check the sell order with the smallest price in the backlog
- If this sell price ≤ buy price, they match and execute:
- The matched quantity is removed from both orders
- Any remaining quantity stays in the backlog
- If no match is possible, the buy order is added to the backlog
When a sell order arrives:
- Check the buy order with the largest price in the backlog
- If this buy price ≥ sell price, they match and execute:
- The matched quantity is removed from both orders
- Any remaining quantity stays in the backlog
- If no match is possible, the sell order is added to the backlog
Orders match and execute as much as possible before being added to the backlog. Each orders[i]
represents amount_i
independent orders at the same price that are processed together.
Return the total number of orders remaining in the backlog after processing all input orders. Since the result can be large, return it modulo 10^9 + 7
.
Intuition
The key insight is recognizing that this is a classic order matching system where we need to efficiently find:
- For buy orders: the minimum sell price to check for matches
- For sell orders: the maximum buy price to check for matches
This immediately suggests using heaps (priority queues) - a min-heap for sell orders and a max-heap for buy orders. This gives us O(log n) insertion and removal of the best matching price.
Why do we need these specific extremes? Because:
- A buyer wants the cheapest available sell price (they want to pay as little as possible)
- A seller wants the highest available buy price (they want to receive as much as possible)
The matching logic follows a greedy approach: when a new order comes in, we keep matching it with the best available counter-orders until either:
- The new order is fully matched (amount becomes 0)
- No more favorable matches exist (prices don't overlap)
For the max-heap implementation of buy orders, we use a trick: store negative prices (-price, amount)
in a min-heap. This effectively converts it to a max-heap since the smallest negative value corresponds to the largest positive value.
The simulation naturally handles partial matches - if an order can't be fully matched, we execute what we can and return the remainder to the appropriate backlog. This ensures all possible trades are executed before adding unmatched portions to the backlog.
Finally, counting the remaining orders is straightforward: sum all amounts in both heaps and apply the modulo operation to handle large numbers.
Learn more about Heap (Priority Queue) patterns.
Solution Approach
We use two heaps to maintain the backlog:
buy
: A max-heap for buy orders (implemented using negative prices in a min-heap)sell
: A min-heap for sell orders
Implementation Steps:
-
Initialize Data Structures:
- Create empty lists
buy
andsell
to serve as heaps - These will store tuples
(price, amount)
- Create empty lists
-
Process Each Order: For each order
[p, a, t]
wherep
is price,a
is amount, andt
is type:If Buy Order (t == 0):
- While amount
a
> 0 and there are sell orders with price ≤p
:- Pop the minimum sell order
(x, y)
fromsell
heap - Match orders:
- If
a >= y
: fully consume the sell order, reducea
byy
- If
a < y
: partially consume, push back(x, y-a)
to sell heap, seta = 0
- If
- Pop the minimum sell order
- If
a > 0
after matching, add(-p, a)
to buy heap (negative for max-heap behavior)
If Sell Order (t == 1):
- While amount
a
> 0 and there are buy orders with price ≥p
:- Pop the maximum buy order
(x, y)
frombuy
heap (rememberx
is negative) - Check if
-x >= p
for matching condition - Match orders:
- If
a >= y
: fully consume the buy order, reducea
byy
- If
a < y
: partially consume, push back(x, y-a)
to buy heap, seta = 0
- If
- Pop the maximum buy order
- If
a > 0
after matching, add(p, a)
to sell heap
- While amount
-
Calculate Final Result:
- Sum all amounts from both heaps:
sum(v[1] for v in buy + sell)
- Return the sum modulo
10^9 + 7
- Sum all amounts from both heaps:
Time Complexity: O(n log n) where n is the total number of orders, as each order might be pushed/popped from the heap once.
Space Complexity: O(n) for storing orders in the heaps.
The algorithm efficiently simulates a real order book by always matching the best available prices first, ensuring optimal trade execution before adding unmatched orders to the backlog.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with orders = [[10, 5, 0], [15, 2, 1], [25, 1, 1], [30, 4, 0]]
Initial State:
- Buy heap: empty
- Sell heap: empty
Step 1: Process [10, 5, 0] (Buy order)
- Price = 10, Amount = 5, Type = Buy
- No sell orders in backlog to match with
- Add to buy heap:
buy = [(-10, 5)]
(using negative price for max-heap)
Step 2: Process [15, 2, 1] (Sell order)
- Price = 15, Amount = 2, Type = Sell
- Check buy heap: largest buy price is 10 (stored as -10)
- Since 10 < 15, no match possible
- Add to sell heap:
sell = [(15, 2)]
- State:
buy = [(-10, 5)]
,sell = [(15, 2)]
Step 3: Process [25, 1, 1] (Sell order)
- Price = 25, Amount = 1, Type = Sell
- Check buy heap: largest buy price is 10
- Since 10 < 25, no match possible
- Add to sell heap:
sell = [(15, 2), (25, 1)]
- State:
buy = [(-10, 5)]
,sell = [(15, 2), (25, 1)]
Step 4: Process [30, 4, 0] (Buy order)
-
Price = 30, Amount = 4, Type = Buy
-
Check sell heap for matches:
First match:
- Smallest sell price is 15, and 15 ≤ 30, so they match!
- Pop (15, 2) from sell heap
- Match 2 units: buy amount reduces from 4 to 2
- Sell order fully consumed
Second match:
- Next smallest sell price is 25, and 25 ≤ 30, so they match!
- Pop (25, 1) from sell heap
- Match 1 unit: buy amount reduces from 2 to 1
- Sell order fully consumed
-
Remaining buy amount = 1
-
Add to buy heap:
buy = [(-10, 5), (-30, 1)]
Final State:
- Buy heap:
[(-10, 5), (-30, 1)]
→ 5 + 1 = 6 orders - Sell heap: empty → 0 orders
- Total backlog: 6 orders
The key insight: The buy order at price 30 was willing to pay up to 30 per unit, so it matched with the cheaper sell orders at 15 and 25 first (best prices for the buyer), executing 3 units total before adding the remaining 1 unit to the backlog.
Solution Implementation
1from typing import List
2import heapq
3
4class Solution:
5 def getNumberOfBacklogOrders(self, orders: List[List[int]]) -> int:
6 # Initialize max heap for buy orders (using negative values for max heap)
7 # and min heap for sell orders
8 buy_backlog = [] # Max heap: stores (-price, amount)
9 sell_backlog = [] # Min heap: stores (price, amount)
10
11 # Process each order
12 for price, amount, order_type in orders:
13 # Buy order (order_type == 0)
14 if order_type == 0:
15 # Try to match with existing sell orders
16 # Buy order can match with sell orders that have price <= current buy price
17 while amount > 0 and sell_backlog and sell_backlog[0][0] <= price:
18 sell_price, sell_amount = heapq.heappop(sell_backlog)
19
20 if amount >= sell_amount:
21 # Current buy order can fully consume this sell order
22 amount -= sell_amount
23 else:
24 # Current buy order is partially consumed
25 # Push back the remaining sell order
26 heapq.heappush(sell_backlog, (sell_price, sell_amount - amount))
27 amount = 0
28
29 # Add remaining buy amount to backlog if any
30 if amount > 0:
31 heapq.heappush(buy_backlog, (-price, amount))
32
33 # Sell order (order_type == 1)
34 else:
35 # Try to match with existing buy orders
36 # Sell order can match with buy orders that have price >= current sell price
37 while amount > 0 and buy_backlog and -buy_backlog[0][0] >= price:
38 neg_buy_price, buy_amount = heapq.heappop(buy_backlog)
39
40 if amount >= buy_amount:
41 # Current sell order can fully consume this buy order
42 amount -= buy_amount
43 else:
44 # Current sell order is partially consumed
45 # Push back the remaining buy order
46 heapq.heappush(buy_backlog, (neg_buy_price, buy_amount - amount))
47 amount = 0
48
49 # Add remaining sell amount to backlog if any
50 if amount > 0:
51 heapq.heappush(sell_backlog, (price, amount))
52
53 # Calculate total backlog orders
54 MOD = 10**9 + 7
55 total_backlog = sum(order[1] for order in buy_backlog + sell_backlog)
56
57 return total_backlog % MOD
58
1class Solution {
2 public int getNumberOfBacklogOrders(int[][] orders) {
3 // Max heap for buy orders (sorted by price in descending order)
4 PriorityQueue<int[]> buyOrders = new PriorityQueue<>((a, b) -> b[0] - a[0]);
5
6 // Min heap for sell orders (sorted by price in ascending order)
7 PriorityQueue<int[]> sellOrders = new PriorityQueue<>((a, b) -> a[0] - b[0]);
8
9 // Process each order
10 for (int[] order : orders) {
11 int price = order[0];
12 int amount = order[1];
13 int orderType = order[2]; // 0 = buy, 1 = sell
14
15 if (orderType == 0) { // Buy order
16 // Try to match with existing sell orders
17 while (amount > 0 && !sellOrders.isEmpty() && sellOrders.peek()[0] <= price) {
18 int[] topSellOrder = sellOrders.poll();
19 int sellPrice = topSellOrder[0];
20 int sellAmount = topSellOrder[1];
21
22 if (amount >= sellAmount) {
23 // Complete the sell order
24 amount -= sellAmount;
25 } else {
26 // Partially fulfill the sell order
27 sellOrders.offer(new int[] {sellPrice, sellAmount - amount});
28 amount = 0;
29 }
30 }
31
32 // Add remaining buy amount to backlog
33 if (amount > 0) {
34 buyOrders.offer(new int[] {price, amount});
35 }
36
37 } else { // Sell order
38 // Try to match with existing buy orders
39 while (amount > 0 && !buyOrders.isEmpty() && buyOrders.peek()[0] >= price) {
40 int[] topBuyOrder = buyOrders.poll();
41 int buyPrice = topBuyOrder[0];
42 int buyAmount = topBuyOrder[1];
43
44 if (amount >= buyAmount) {
45 // Complete the buy order
46 amount -= buyAmount;
47 } else {
48 // Partially fulfill the buy order
49 buyOrders.offer(new int[] {buyPrice, buyAmount - amount});
50 amount = 0;
51 }
52 }
53
54 // Add remaining sell amount to backlog
55 if (amount > 0) {
56 sellOrders.offer(new int[] {price, amount});
57 }
58 }
59 }
60
61 // Calculate total backlog
62 long totalBacklog = 0;
63 final int MOD = (int) 1e9 + 7;
64
65 // Sum all remaining buy orders
66 while (!buyOrders.isEmpty()) {
67 totalBacklog += buyOrders.poll()[1];
68 }
69
70 // Sum all remaining sell orders
71 while (!sellOrders.isEmpty()) {
72 totalBacklog += sellOrders.poll()[1];
73 }
74
75 return (int) (totalBacklog % MOD);
76 }
77}
78
1class Solution {
2public:
3 int getNumberOfBacklogOrders(vector<vector<int>>& orders) {
4 // Define pair type for (price, amount)
5 using PriceAmount = pair<int, int>;
6
7 // Min heap for sell orders (sorted by lowest price first)
8 priority_queue<PriceAmount, vector<PriceAmount>, greater<PriceAmount>> sellOrders;
9
10 // Max heap for buy orders (sorted by highest price first)
11 priority_queue<PriceAmount> buyOrders;
12
13 // Process each order
14 for (auto& order : orders) {
15 int price = order[0];
16 int amount = order[1];
17 int orderType = order[2]; // 0 for buy, 1 for sell
18
19 if (orderType == 0) { // Buy order
20 // Match with existing sell orders while possible
21 while (amount > 0 && !sellOrders.empty() && sellOrders.top().first <= price) {
22 auto [sellPrice, sellAmount] = sellOrders.top();
23 sellOrders.pop();
24
25 if (amount >= sellAmount) {
26 // Buy order can fully consume this sell order
27 amount -= sellAmount;
28 } else {
29 // Sell order is partially consumed, push back remaining
30 sellOrders.push({sellPrice, sellAmount - amount});
31 amount = 0;
32 }
33 }
34
35 // Add remaining buy amount to backlog if any
36 if (amount > 0) {
37 buyOrders.push({price, amount});
38 }
39
40 } else { // Sell order
41 // Match with existing buy orders while possible
42 while (amount > 0 && !buyOrders.empty() && buyOrders.top().first >= price) {
43 auto [buyPrice, buyAmount] = buyOrders.top();
44 buyOrders.pop();
45
46 if (amount >= buyAmount) {
47 // Sell order can fully consume this buy order
48 amount -= buyAmount;
49 } else {
50 // Buy order is partially consumed, push back remaining
51 buyOrders.push({buyPrice, buyAmount - amount});
52 amount = 0;
53 }
54 }
55
56 // Add remaining sell amount to backlog if any
57 if (amount > 0) {
58 sellOrders.push({price, amount});
59 }
60 }
61 }
62
63 // Calculate total backlog orders
64 long totalBacklog = 0;
65
66 // Sum all remaining buy orders
67 while (!buyOrders.empty()) {
68 totalBacklog += buyOrders.top().second;
69 buyOrders.pop();
70 }
71
72 // Sum all remaining sell orders
73 while (!sellOrders.empty()) {
74 totalBacklog += sellOrders.top().second;
75 sellOrders.pop();
76 }
77
78 // Return result modulo 10^9 + 7
79 const int MOD = 1e9 + 7;
80 return totalBacklog % MOD;
81 }
82};
83
1function getNumberOfBacklogOrders(orders: number[][]): number {
2 // Min heap for sell orders (sorted by lowest price first)
3 // Each element is [price, amount]
4 const sellOrders: number[][] = [];
5
6 // Max heap for buy orders (sorted by highest price first)
7 // Each element is [price, amount]
8 const buyOrders: number[][] = [];
9
10 // Helper function to maintain min heap property for sell orders
11 const pushSellOrder = (price: number, amount: number): void => {
12 sellOrders.push([price, amount]);
13 sellOrders.sort((a, b) => a[0] - b[0]);
14 };
15
16 // Helper function to maintain max heap property for buy orders
17 const pushBuyOrder = (price: number, amount: number): void => {
18 buyOrders.push([price, amount]);
19 buyOrders.sort((a, b) => b[0] - a[0]);
20 };
21
22 // Process each order
23 for (const order of orders) {
24 let price = order[0];
25 let amount = order[1];
26 const orderType = order[2]; // 0 for buy, 1 for sell
27
28 if (orderType === 0) { // Buy order
29 // Match with existing sell orders while possible
30 while (amount > 0 && sellOrders.length > 0 && sellOrders[0][0] <= price) {
31 const sellPrice = sellOrders[0][0];
32 const sellAmount = sellOrders[0][1];
33 sellOrders.shift(); // Remove the top sell order
34
35 if (amount >= sellAmount) {
36 // Buy order can fully consume this sell order
37 amount -= sellAmount;
38 } else {
39 // Sell order is partially consumed, push back remaining
40 pushSellOrder(sellPrice, sellAmount - amount);
41 amount = 0;
42 }
43 }
44
45 // Add remaining buy amount to backlog if any
46 if (amount > 0) {
47 pushBuyOrder(price, amount);
48 }
49
50 } else { // Sell order
51 // Match with existing buy orders while possible
52 while (amount > 0 && buyOrders.length > 0 && buyOrders[0][0] >= price) {
53 const buyPrice = buyOrders[0][0];
54 const buyAmount = buyOrders[0][1];
55 buyOrders.shift(); // Remove the top buy order
56
57 if (amount >= buyAmount) {
58 // Sell order can fully consume this buy order
59 amount -= buyAmount;
60 } else {
61 // Buy order is partially consumed, push back remaining
62 pushBuyOrder(buyPrice, buyAmount - amount);
63 amount = 0;
64 }
65 }
66
67 // Add remaining sell amount to backlog if any
68 if (amount > 0) {
69 pushSellOrder(price, amount);
70 }
71 }
72 }
73
74 // Calculate total backlog orders
75 let totalBacklog = 0;
76
77 // Sum all remaining buy orders
78 for (const buyOrder of buyOrders) {
79 totalBacklog += buyOrder[1];
80 }
81
82 // Sum all remaining sell orders
83 for (const sellOrder of sellOrders) {
84 totalBacklog += sellOrder[1];
85 }
86
87 // Return result modulo 10^9 + 7
88 const MOD = 1e9 + 7;
89 return totalBacklog % MOD;
90}
91
Time and Space Complexity
Time Complexity: O(n × log n)
where n
is the length of orders
.
The algorithm iterates through each order once (O(n)
). For each order, in the worst case, we perform multiple heap operations (push and pop). Each heap operation takes O(log k)
time where k
is the size of the heap. In the worst case, the heap can contain up to n
elements (when all orders remain in the backlog). While we might perform multiple heap operations per order due to the while loops, the total number of heap operations across all orders is bounded by O(n)
since each order can only be pushed once and popped once. Therefore, the overall time complexity is O(n × log n)
.
Space Complexity: O(n)
where n
is the length of orders
.
The space is primarily used by the two heaps (buy
and sell
). In the worst case, all orders could remain unmatched and stay in the backlog, meaning all n
orders would be stored in the heaps. Each heap element stores a tuple of price and amount, which takes constant space. Therefore, the total space complexity is O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Heap Implementation for Buy Orders
Pitfall: The most common mistake is forgetting that Python's heapq
only provides a min-heap implementation. When dealing with buy orders, we need a max-heap to always get the highest buy price first. Many developers might incorrectly use:
# WRONG: This gives us the minimum buy price, not maximum heapq.heappush(buy_backlog, (price, amount))
Solution: Store negative prices for buy orders to simulate max-heap behavior:
# CORRECT: Negative price converts min-heap to max-heap behavior heapq.heappush(buy_backlog, (-price, amount)) # When popping, remember to negate back for comparison
2. Forgetting to Handle Partial Order Matching
Pitfall: When orders partially match, developers often forget to push the remaining portion back to the heap:
# WRONG: Loses the unmatched portion of the order if amount < sell_amount: amount = 0 # Missing: push back (sell_price, sell_amount - amount)
Solution: Always push back the remaining quantity after partial matching:
# CORRECT: Preserves unmatched portion in the backlog if amount < sell_amount: heapq.heappush(sell_backlog, (sell_price, sell_amount - amount)) amount = 0
3. Integer Overflow When Calculating Total Backlog
Pitfall: Forgetting to apply modulo during or after summation can cause issues with large test cases:
# RISKY: Sum might exceed integer limits before modulo
total_backlog = sum(order[1] for order in buy_backlog + sell_backlog)
return total_backlog % MOD
Solution: While Python handles big integers well, in other languages or for better practice, consider applying modulo during accumulation:
# SAFER: Apply modulo during accumulation to prevent overflow total_backlog = 0 MOD = 10**9 + 7 for _, amount in buy_backlog + sell_backlog: total_backlog = (total_backlog + amount) % MOD return total_backlog
4. Incorrect Order Matching Conditions
Pitfall: Mixing up the price comparison logic for buy and sell orders:
# WRONG: Buy orders should match when sell_price <= buy_price while sell_backlog and sell_backlog[0][0] >= price: # Wrong operator # WRONG: Forgetting to negate the buy price when comparing while buy_backlog and buy_backlog[0][0] >= price: # Should be -buy_backlog[0][0]
Solution: Remember the matching rules:
- Buy orders match with sells when:
sell_price <= buy_price
- Sell orders match with buys when:
buy_price >= sell_price
(and buy prices are stored as negative)
# CORRECT for buy orders: while sell_backlog and sell_backlog[0][0] <= price: # CORRECT for sell orders (with negated buy prices): while buy_backlog and -buy_backlog[0][0] >= price:
What's the output of running the following function using the following tree as input?
1def serialize(root):
2 res = []
3 def dfs(root):
4 if not root:
5 res.append('x')
6 return
7 res.append(root.val)
8 dfs(root.left)
9 dfs(root.right)
10 dfs(root)
11 return ' '.join(res)
12
1import java.util.StringJoiner;
2
3public static String serialize(Node root) {
4 StringJoiner res = new StringJoiner(" ");
5 serializeDFS(root, res);
6 return res.toString();
7}
8
9private static void serializeDFS(Node root, StringJoiner result) {
10 if (root == null) {
11 result.add("x");
12 return;
13 }
14 result.add(Integer.toString(root.val));
15 serializeDFS(root.left, result);
16 serializeDFS(root.right, result);
17}
18
1function serialize(root) {
2 let res = [];
3 serialize_dfs(root, res);
4 return res.join(" ");
5}
6
7function serialize_dfs(root, res) {
8 if (!root) {
9 res.push("x");
10 return;
11 }
12 res.push(root.val);
13 serialize_dfs(root.left, res);
14 serialize_dfs(root.right, res);
15}
16
Recommended Readings
https assets algo monster cover_photos heap svg Priority Queue and Heap What is the relationship between priority queue and heap Priority Queue is an Abstract Data Type and Heap is the concrete data structure we use to implement a priority queue Priority Queue A priority queue is a data structure
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!