Facebook Pixel

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 unit
  • amount_i: the number of orders at this price
  • orderType_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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. The new order is fully matched (amount becomes 0)
  2. 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:

  1. Initialize Data Structures:

    • Create empty lists buy and sell to serve as heaps
    • These will store tuples (price, amount)
  2. Process Each Order: For each order [p, a, t] where p is price, a is amount, and t 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) from sell heap
      • Match orders:
        • If a >= y: fully consume the sell order, reduce a by y
        • If a < y: partially consume, push back (x, y-a) to sell heap, set a = 0
    • 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) from buy heap (remember x is negative)
      • Check if -x >= p for matching condition
      • Match orders:
        • If a >= y: fully consume the buy order, reduce a by y
        • If a < y: partially consume, push back (x, y-a) to buy heap, set a = 0
    • If a > 0 after matching, add (p, a) to sell heap
  3. Calculate Final Result:

    • Sum all amounts from both heaps: sum(v[1] for v in buy + sell)
    • Return the sum modulo 10^9 + 7

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 Evaluator

Example 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:
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

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

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More