Facebook Pixel

2940. Find Building Where Alice and Bob Can Meet

Problem Description

You are given a 0-indexed array heights of positive integers, where heights[i] represents the height of the i-th building.

The movement rule for a person in the buildings is: If a person is in building i, they can move to any other building j if and only if both conditions are met:

  • i < j (can only move to buildings with higher indices)
  • heights[i] < heights[j] (can only move to taller buildings)

You are also given an array queries where queries[i] = [ai, bi]. For each query:

  • Alice starts in building ai
  • Bob starts in building bi

Your task is to find the leftmost building (minimum index) where Alice and Bob can meet for each query. Both Alice and Bob need to be able to reach this common building following the movement rules.

Return an array ans where ans[i] is the index of the leftmost building where Alice and Bob can meet on the i-th query. If they cannot meet at any building, set ans[i] to -1.

For example, if Alice is at building 2 and Bob is at building 5:

  • Alice can move from building 2 to any building j > 2 where heights[j] > heights[2]
  • Bob can move from building 5 to any building j > 5 where heights[j] > heights[5]
  • The answer would be the smallest index building that both can reach

Note that if Alice and Bob start at the same building, or if one person is already at a building the other can reach directly, that building itself could be the meeting point.

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

Intuition

Let's think about when Alice and Bob can meet. For a query [a, b], we can assume a ≤ b without loss of generality (if not, we can swap them).

There are a few cases to consider:

  1. If a == b: They're already at the same building, so the answer is b.

  2. If heights[a] < heights[b]: Alice can directly jump to building b (since a < b and heights[a] < heights[b]), so they meet at building b.

  3. If heights[a] ≥ heights[b]: This is the tricky case. Bob cannot jump backwards to Alice's building. Alice cannot jump to Bob's building because heights[a] ≥ heights[b]. They need to find a common building j where j > b and heights[j] > heights[a] (so Alice can reach it) and heights[j] > heights[b] (so Bob can reach it). Since heights[a] ≥ heights[b], we just need heights[j] > heights[a].

The key insight is that for case 3, we need to find the leftmost (smallest index) building j such that j > b and heights[j] > heights[a].

Now, how do we efficiently find this for multiple queries? If we process queries independently, we'd need to scan the array for each query, which would be inefficient.

The clever approach is to process queries in descending order of their right endpoint b. Why? Because as we process queries with smaller b values, we can incrementally add buildings from right to left into a data structure. This way, when we process a query with right endpoint b, we already have information about all buildings with index greater than b.

We use a Binary Indexed Tree (Fenwick Tree) to maintain the minimum index for each height value. As we move from right to left in the buildings array, we insert each building's index into the BIT, indexed by its height (after discretization to handle the range). When we need to find the meeting point for Alice at position a, we query the BIT for the minimum index among all buildings taller than heights[a].

The discretization step (using sorted(set(heights))) is needed because heights can be large values, but we need them as indices for the BIT. We map each height to its rank among all unique heights.

Learn more about Stack, Segment Tree, Binary Search, Monotonic Stack and Heap (Priority Queue) patterns.

Solution Approach

The solution uses a Binary Indexed Tree (Fenwick Tree) combined with query sorting to efficiently find the leftmost meeting building for each query.

Step 1: Query Preprocessing

  • First, normalize each query so that queries[i] = [min(ai, bi), max(ai, bi)]. This ensures l ≤ r for each query [l, r].
  • Sort query indices in descending order of their right endpoint r. This allows us to process buildings from right to left and reuse computed information.

Step 2: Height Discretization

  • Create a sorted list of unique heights: s = sorted(set(heights)).
  • This is used to map actual height values to ranks (1 to n) for indexing in the BIT.
  • For a height h, its rank is n - bisect_left(s, h). We use reverse ranking so that larger heights get smaller indices, which aligns with our query needs.

Step 3: Binary Indexed Tree Setup

  • Initialize a BIT of size n that maintains the minimum index for each height rank.
  • The BIT supports:
    • update(x, v): Update position x with value v (keeping minimum)
    • query(x): Get the minimum value among positions 1 to x

Step 4: Process Queries in Order

  • Use pointer j starting from n-1 (rightmost building).

  • For each query [l, r] (processed in descending order of r):

    a. Add buildings to BIT: While j > r, insert building j into the BIT:

    • Calculate height rank: k = n - bisect_left(s, heights[j]) + 1
    • Update BIT at position k with value j
    • Decrement j

    b. Answer the query:

    • If l == r or heights[l] < heights[r]: Answer is r (direct meeting)
    • Otherwise:
      • Calculate rank of heights[l]: k = n - bisect_left(s, heights[l])
      • Query BIT for minimum index among buildings taller than heights[l]
      • The BIT returns the leftmost valid building index, or -1 if none exists

Why this works:

  • By processing queries with larger r values first, we ensure that when processing a query with endpoint r, the BIT already contains all buildings with index > r.
  • The BIT efficiently maintains the minimum index for each height range, allowing O(log n) queries.
  • The reverse ranking ensures that querying rank k gives us the minimum index among all buildings with height strictly greater than the height at rank k.

Time Complexity: O((n + m) × log n + m × log m)

  • Sorting queries: O(m log m)
  • Processing each building and query: O((n + m) × log n) due to BIT operations

Space Complexity: O(n + m) for the BIT and auxiliary arrays.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example to illustrate the solution approach.

Input:

  • heights = [5, 3, 8, 2, 6, 1, 4, 6]
  • queries = [[0, 7], [3, 5], [5, 2], [3, 0], [1, 6]]

Step 1: Query Preprocessing

Normalize queries so left ≤ right:

  • [0, 7][0, 7] (already normalized)
  • [3, 5][3, 5] (already normalized)
  • [5, 2][2, 5] (swapped)
  • [3, 0][0, 3] (swapped)
  • [1, 6][1, 6] (already normalized)

Sort query indices by descending right endpoint:

  • Query 0: [0, 7] (r=7)
  • Query 4: [1, 6] (r=6)
  • Query 1: [3, 5] (r=5)
  • Query 2: [2, 5] (r=5)
  • Query 3: [0, 3] (r=3)

Step 2: Height Discretization

Unique heights sorted: s = [1, 2, 3, 4, 5, 6, 8]

Height rankings (using reverse order):

  • Height 8 → rank 1
  • Height 6 → rank 2
  • Height 5 → rank 3
  • Height 4 → rank 4
  • Height 3 → rank 5
  • Height 2 → rank 6
  • Height 1 → rank 7

Step 3: Process Queries

Initialize empty BIT and pointer j = 7 (rightmost building).

Processing Query 0: [0, 7]

  • Add buildings from index 7 down to index 8 (none to add since j=7 and r=7)
  • Check: l=0, r=7, heights[0]=5 < heights[7]=6
  • Direct jump possible! Answer: 7

Processing Query 4: [1, 6]

  • Add building 7 to BIT:
    • heights[7]=6 → rank 2
    • BIT.update(2, 7)
  • Check: l=1, r=6, heights[1]=3 < heights[6]=4
  • Direct jump possible! Answer: 6

Processing Query 1: [3, 5]

  • Add building 6 to BIT:
    • heights[6]=4 → rank 4
    • BIT.update(4, 6)
  • Check: l=3, r=5, heights[3]=2 > heights[5]=1
  • Need to find building > index 5 with height > 2
  • Query BIT for heights > 2 (rank 6): BIT.query(5) returns 6
  • Answer: 6 (building at index 6 has height 4 > 2)

Processing Query 2: [2, 5]

  • No new buildings to add (r=5 same as previous)
  • Check: l=2, r=5, heights[2]=8 > heights[5]=1
  • Need to find building > index 5 with height > 8
  • Query BIT for heights > 8 (rank 0): No buildings taller than 8
  • Answer: -1

Processing Query 3: [0, 3]

  • Add buildings 5, 4, 3 to BIT:
    • Building 5: heights[5]=1 → rank 7, BIT.update(7, 5)
    • Building 4: heights[4]=6 → rank 2, BIT.update(2, 4)
    • Building 3: Already processed as part of query
  • Check: l=0, r=3, heights[0]=5 > heights[3]=2
  • Need to find building > index 3 with height > 5
  • Query BIT for heights > 5 (rank 2): BIT.query(2) returns 4
  • Answer: 4 (building at index 4 has height 6 > 5)

Final Results: Reorder answers to match original query order:

  • Query 0: 7
  • Query 1: 6
  • Query 2: -1
  • Query 3: 4
  • Query 4: 6

Output: [7, 6, -1, 4, 6]

Solution Implementation

1from typing import List
2from bisect import bisect_left
3
4class BinaryIndexedTree:
5    """
6    A Binary Indexed Tree (Fenwick Tree) modified to support range minimum queries.
7    Used to efficiently find the minimum index for heights greater than a threshold.
8    """
9    __slots__ = ["size", "tree"]
10
11    def __init__(self, size: int):
12        """
13        Initialize the BIT with given size.
14      
15        Args:
16            size: The size of the array to index
17        """
18        self.size = size
19        self.tree = [float('inf')] * (size + 1)
20
21    def update(self, index: int, value: int):
22        """
23        Update the BIT by setting minimum value at position index.
24      
25        Args:
26            index: The position to update (1-indexed)
27            value: The value to set as minimum
28        """
29        while index <= self.size:
30            self.tree[index] = min(self.tree[index], value)
31            # Move to next index affected by this position
32            index += index & -index
33
34    def query(self, index: int) -> int:
35        """
36        Query the minimum value from index 1 to the given index.
37      
38        Args:
39            index: The right boundary of the query range (1-indexed)
40          
41        Returns:
42            The minimum value in range [1, index], or -1 if no valid value exists
43        """
44        minimum = float('inf')
45        while index > 0:
46            minimum = min(minimum, self.tree[index])
47            # Move to parent node in BIT
48            index -= index & -index
49        return -1 if minimum == float('inf') else minimum
50
51
52class Solution:
53    def leftmostBuildingQueries(
54        self, heights: List[int], queries: List[List[int]]
55    ) -> List[int]:
56        """
57        Find the leftmost building where pairs of people can meet.
58      
59        For each query [a, b], find the leftmost building c where:
60        - c >= max(a, b)
61        - heights[c] > max(heights[a], heights[b])
62      
63        Args:
64            heights: List of building heights
65            queries: List of pairs [a, b] representing people positions
66          
67        Returns:
68            List of leftmost meeting positions for each query, -1 if impossible
69        """
70        num_buildings = len(heights)
71        num_queries = len(queries)
72      
73        # Normalize queries so that first element is always smaller
74        for i in range(num_queries):
75            queries[i] = [min(queries[i]), max(queries[i])]
76      
77        # Create sorted unique heights for coordinate compression
78        sorted_unique_heights = sorted(set(heights))
79      
80        # Initialize result array
81        result = [-1] * num_queries
82      
83        # Initialize BIT for efficient range minimum queries
84        fenwick_tree = BinaryIndexedTree(num_buildings)
85      
86        # Current building index being processed (right to left)
87        current_building = num_buildings - 1
88      
89        # Process queries in descending order of right endpoint
90        # This allows us to incrementally build the BIT as we go
91        sorted_query_indices = sorted(range(num_queries), 
92                                     key=lambda i: -queries[i][1])
93      
94        for query_idx in sorted_query_indices:
95            left_person, right_person = queries[query_idx]
96          
97            # Add all buildings to the right of current query's right endpoint
98            while current_building > right_person:
99                # Compress height coordinate (1-indexed for BIT)
100                # Higher heights get lower compressed values for minimum queries
101                compressed_height = num_buildings - bisect_left(sorted_unique_heights, 
102                                                               heights[current_building]) + 1
103                fenwick_tree.update(compressed_height, current_building)
104                current_building -= 1
105          
106            # Check if people can meet at the right person's position
107            if left_person == right_person or heights[left_person] < heights[right_person]:
108                result[query_idx] = right_person
109            else:
110                # Need to find a building taller than left person's building
111                # Query for buildings strictly taller than left person's height
112                compressed_threshold = num_buildings - bisect_left(sorted_unique_heights, 
113                                                                  heights[left_person])
114                result[query_idx] = fenwick_tree.query(compressed_threshold)
115      
116        return result
117
1class BinaryIndexedTree {
2    private static final int INFINITY = 1 << 30;
3    private int size;
4    private int[] tree;
5
6    /**
7     * Initializes a Binary Indexed Tree (Fenwick Tree) for range minimum queries
8     * @param size The size of the tree
9     */
10    public BinaryIndexedTree(int size) {
11        this.size = size;
12        this.tree = new int[size + 1];
13        Arrays.fill(this.tree, INFINITY);
14    }
15
16    /**
17     * Updates the tree by setting the minimum value at position and propagating upward
18     * @param position The 1-indexed position to update
19     * @param value The value to potentially set as minimum
20     */
21    public void update(int position, int value) {
22        while (position <= size) {
23            tree[position] = Math.min(tree[position], value);
24            position += position & -position; // Move to next position using lowbit
25        }
26    }
27
28    /**
29     * Queries the minimum value in range [1, position]
30     * @param position The 1-indexed position up to which to query
31     * @return The minimum value found, or -1 if no valid value exists
32     */
33    public int query(int position) {
34        int minimum = INFINITY;
35        while (position > 0) {
36            minimum = Math.min(minimum, tree[position]);
37            position -= position & -position; // Move to parent using lowbit
38        }
39        return minimum == INFINITY ? -1 : minimum;
40    }
41}
42
43class Solution {
44    /**
45     * Finds the leftmost building where each pair of people can meet
46     * @param heights Array of building heights
47     * @param queries Array of query pairs [person1, person2]
48     * @return Array of leftmost meeting positions for each query
49     */
50    public int[] leftmostBuildingQueries(int[] heights, int[][] queries) {
51        int buildingCount = heights.length;
52        int queryCount = queries.length;
53      
54        // Normalize queries so that left index is always smaller than right
55        for (int i = 0; i < queryCount; ++i) {
56            if (queries[i][0] > queries[i][1]) {
57                queries[i] = new int[] {queries[i][1], queries[i][0]};
58            }
59        }
60      
61        // Create index array for sorting queries
62        Integer[] queryIndices = new Integer[queryCount];
63        for (int i = 0; i < queryCount; ++i) {
64            queryIndices[i] = i;
65        }
66      
67        // Sort query indices by right endpoint in descending order
68        Arrays.sort(queryIndices, (i, j) -> queries[j][1] - queries[i][1]);
69      
70        // Create sorted copy of heights for binary search
71        int[] sortedHeights = heights.clone();
72        Arrays.sort(sortedHeights);
73      
74        int[] results = new int[queryCount];
75        int currentBuildingIndex = buildingCount - 1;
76        BinaryIndexedTree fenwickTree = new BinaryIndexedTree(buildingCount);
77      
78        // Process queries from right to left
79        for (int queryIndex : queryIndices) {
80            int leftPerson = queries[queryIndex][0];
81            int rightPerson = queries[queryIndex][1];
82          
83            // Add buildings to the right of current query's right endpoint to the tree
84            while (currentBuildingIndex > rightPerson) {
85                // Find rank of current building's height (1-indexed from highest)
86                int heightRank = buildingCount - Arrays.binarySearch(sortedHeights, heights[currentBuildingIndex]) + 1;
87                fenwickTree.update(heightRank, currentBuildingIndex);
88                --currentBuildingIndex;
89            }
90          
91            // Check if people can meet at right person's position
92            if (leftPerson == rightPerson || heights[leftPerson] < heights[rightPerson]) {
93                results[queryIndex] = rightPerson;
94            } else {
95                // Find leftmost building taller than left person's building
96                int minHeightRank = buildingCount - Arrays.binarySearch(sortedHeights, heights[leftPerson]);
97                results[queryIndex] = fenwickTree.query(minHeightRank);
98            }
99        }
100      
101        return results;
102    }
103}
104
1class BinaryIndexedTree {
2private:
3    int INF = 1 << 30;  // Large value representing infinity
4    int size;           // Size of the tree
5    vector<int> tree;   // BIT array storing minimum values
6  
7public:
8    // Constructor: Initialize BIT with given size
9    BinaryIndexedTree(int n) {
10        this->size = n;
11        tree.resize(n + 1, INF);  // 1-indexed array initialized with INF
12    }
13  
14    // Update operation: Update position x with minimum value v
15    void update(int x, int v) {
16        while (x <= size) {
17            tree[x] = min(tree[x], v);  // Store minimum value at position x
18            x += x & -x;  // Move to next position using lowbit operation
19        }
20    }
21  
22    // Query operation: Find minimum value in range [1, x]
23    int query(int x) {
24        int minVal = INF;
25        while (x > 0) {
26            minVal = min(minVal, tree[x]);  // Track minimum value
27            x -= x & -x;  // Move to previous position using lowbit operation
28        }
29        return minVal == INF ? -1 : minVal;  // Return -1 if no valid value found
30    }
31};
32
33class Solution {
34public:
35    vector<int> leftmostBuildingQueries(vector<int>& heights, vector<vector<int>>& queries) {
36        int n = heights.size();
37        int m = queries.size();
38      
39        // Normalize queries: ensure first index is smaller than second
40        for (auto& query : queries) {
41            if (query[0] > query[1]) {
42                swap(query[0], query[1]);
43            }
44        }
45      
46        // Create index array for sorting queries
47        vector<int> queryIndices(m);
48        iota(queryIndices.begin(), queryIndices.end(), 0);
49      
50        // Sort query indices by right endpoint in descending order
51        sort(queryIndices.begin(), queryIndices.end(), [&](int i, int j) {
52            return queries[j][1] < queries[i][1];
53        });
54      
55        // Create sorted unique heights for coordinate compression
56        vector<int> sortedHeights = heights;
57        sort(sortedHeights.begin(), sortedHeights.end());
58        sortedHeights.erase(unique(sortedHeights.begin(), sortedHeights.end()), sortedHeights.end());
59      
60        vector<int> answer(m);
61        int currentPosition = n - 1;  // Start from the rightmost building
62        BinaryIndexedTree fenwickTree(n);
63      
64        // Process queries in sorted order
65        for (int queryIdx : queryIndices) {
66            int leftIdx = queries[queryIdx][0];
67            int rightIdx = queries[queryIdx][1];
68          
69            // Process buildings from right to left until we reach the query's right endpoint
70            while (currentPosition > rightIdx) {
71                // Find compressed coordinate for current height (reverse order)
72                int compressedCoord = sortedHeights.end() - 
73                                     lower_bound(sortedHeights.begin(), sortedHeights.end(), heights[currentPosition]) + 1;
74                fenwickTree.update(compressedCoord, currentPosition);
75                --currentPosition;
76            }
77          
78            // Check if Alice and Bob can meet at the right position
79            if (leftIdx == rightIdx || heights[leftIdx] < heights[rightIdx]) {
80                answer[queryIdx] = rightIdx;
81            } else {
82                // Find the leftmost building higher than heights[leftIdx]
83                int compressedCoord = sortedHeights.end() - 
84                                     lower_bound(sortedHeights.begin(), sortedHeights.end(), heights[leftIdx]);
85                answer[queryIdx] = fenwickTree.query(compressedCoord);
86            }
87        }
88      
89        return answer;
90    }
91};
92
1// Binary Indexed Tree (Fenwick Tree) for range minimum queries
2// Stores the minimum value for each prefix
3let n: number;
4let c: number[];
5const INF: number = 1 << 30;
6
7// Initialize the BIT with size n
8function initBIT(size: number): void {
9    n = size;
10    c = Array(size + 1).fill(INF);
11}
12
13// Update the BIT at position x with value v (keeping minimum)
14// Uses the BIT update pattern: x += x & -x to traverse parent nodes
15function update(x: number, v: number): void {
16    while (x <= n) {
17        c[x] = Math.min(c[x], v);
18        x += x & -x; // Move to next position by adding lowest set bit
19    }
20}
21
22// Query the minimum value in range [1, x]
23// Uses the BIT query pattern: x -= x & -x to traverse the tree
24function query(x: number): number {
25    let minValue = INF;
26    while (x > 0) {
27        minValue = Math.min(minValue, c[x]);
28        x -= x & -x; // Move to parent by removing lowest set bit
29    }
30    return minValue === INF ? -1 : minValue;
31}
32
33// Binary search to find the first index where sorted array value >= target
34function binarySearch(sortedArray: number[], target: number, arrayLength: number): number {
35    let left = 0;
36    let right = arrayLength;
37  
38    while (left < right) {
39        const mid = (left + right) >> 1; // Equivalent to Math.floor((left + right) / 2)
40        if (sortedArray[mid] >= target) {
41            right = mid;
42        } else {
43            left = mid + 1;
44        }
45    }
46    return left;
47}
48
49function leftmostBuildingQueries(heights: number[], queries: number[][]): number[] {
50    const numBuildings = heights.length;
51    const numQueries = queries.length;
52  
53    // Normalize queries so that first index is always smaller
54    for (const query of queries) {
55        if (query[0] > query[1]) {
56            [query[0], query[1]] = [query[1], query[0]];
57        }
58    }
59  
60    // Create array of query indices [0, 1, 2, ..., m-1]
61    const queryIndices: number[] = Array(numQueries)
62        .fill(0)
63        .map((_, i) => i);
64  
65    // Sort query indices by the second element of each query in descending order
66    queryIndices.sort((i, j) => queries[j][1] - queries[i][1]);
67  
68    // Initialize Binary Indexed Tree
69    initBIT(numBuildings);
70  
71    // Initialize result array with -1 (no valid building found)
72    const result: number[] = Array(numQueries).fill(-1);
73  
74    // Create sorted copy of heights for binary search
75    const sortedHeights = [...heights];
76    sortedHeights.sort((a, b) => a - b);
77  
78    // Process buildings from right to left
79    let currentBuildingIndex = numBuildings - 1;
80  
81    // Process queries in order of descending right boundary
82    for (const queryIndex of queryIndices) {
83        const [leftBound, rightBound] = queries[queryIndex];
84      
85        // Add all buildings to the right of current query's right boundary to BIT
86        while (currentBuildingIndex > rightBound) {
87            // Find rank of current building height in sorted array
88            // Using complement (n - searchResult + 1) to store in BIT
89            const rank = numBuildings - binarySearch(sortedHeights, heights[currentBuildingIndex], numBuildings) + 1;
90            update(rank, currentBuildingIndex);
91            currentBuildingIndex--;
92        }
93      
94        // Handle special cases
95        if (leftBound === rightBound || heights[leftBound] < heights[rightBound]) {
96            // If same building or left is shorter, right boundary is the answer
97            result[queryIndex] = rightBound;
98        } else {
99            // Find the leftmost building taller than heights[leftBound]
100            const rank = numBuildings - binarySearch(sortedHeights, heights[leftBound], numBuildings);
101            result[queryIndex] = query(rank);
102        }
103    }
104  
105    return result;
106}
107

Time and Space Complexity

Time Complexity: O(m log m + (m + n) log n)

The time complexity breaks down as follows:

  • Sorting queries by their right endpoint: O(m log m) where m is the number of queries
  • Creating sorted set of unique heights: O(n log n) where n is the length of heights array
  • Main loop iterates through all m queries:
    • For each query, we potentially process buildings from position j down to r
    • Each building is processed at most once across all queries
    • For each building processed:
      • Binary search using bisect_left: O(log n)
      • BIT update operation: O(log n)
    • Total for processing all buildings: O(n log n)
    • For each query answer:
      • Binary search: O(log n)
      • BIT query operation: O(log n)
    • Total for all query answers: O(m log n)
  • Overall: O(m log m + n log n + n log n + m log n) = O(m log m + (m + n) log n)

Space Complexity: O(n + m)

The space complexity consists of:

  • Binary Indexed Tree array c: O(n)
  • Sorted set of heights s: O(n) in worst case when all heights are unique
  • Answer array: O(m)
  • Sorting operations use O(log m) stack space for sorting queries
  • Total: O(n + m)

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect Height Comparison for Direct Meeting

Pitfall: A common mistake is using heights[left_person] <= heights[right_person] instead of heights[left_person] < heights[right_person] when checking if two people can meet at the right person's position.

Why it's wrong: When heights[left_person] == heights[right_person], the left person cannot move to the right person's building because the movement rule requires moving to a strictly taller building.

Example:

  • heights = [3, 5, 5, 2]
  • Query [1, 2] where both buildings have height 5
  • The left person at index 1 cannot move to index 2 since heights[1] = heights[2] = 5

Solution: Always use strict inequality (<) when checking height conditions:

if left_person == right_person or heights[left_person] < heights[right_person]:
    result[query_idx] = right_person

2. Off-by-One Error in BIT Querying

Pitfall: Using compressed_threshold = num_buildings - bisect_left(sorted_unique_heights, heights[left_person]) + 1 instead of without the +1 when querying the BIT.

Why it's wrong: The BIT query should find buildings strictly taller than heights[left_person]. Adding 1 would include buildings with the same height, violating the movement rule.

Example:

  • If heights[left_person] = 10 and we want buildings with height > 10
  • The compressed rank for height 10 is at position k
  • We need to query positions 1 to k-1 (heights > 10), not 1 to k (heights >= 10)

Solution: Query the BIT with the correct threshold:

compressed_threshold = num_buildings - bisect_left(sorted_unique_heights, heights[left_person])
result[query_idx] = fenwick_tree.query(compressed_threshold)

3. Not Handling the Case When Both Start at Same Building

Pitfall: Forgetting to handle left_person == right_person as a special case.

Why it matters: When both people start at the same building, they're already "meeting" at that building, regardless of height comparisons.

Solution: Always check this condition first:

if left_person == right_person:  # Already at same building
    result[query_idx] = right_person
elif heights[left_person] < heights[right_person]:  # Can meet at right
    result[query_idx] = right_person
else:  # Need to find a taller building
    # ... BIT query logic

4. Incorrect Query Normalization

Pitfall: Not normalizing queries to ensure left <= right, or normalizing incorrectly.

Why it's wrong: The algorithm assumes the left person has a smaller or equal index. Without normalization, the logic breaks because we process buildings from right to left based on the rightmost person's position.

Solution: Always normalize at the beginning:

for i in range(num_queries):
    queries[i] = [min(queries[i]), max(queries[i])]

5. Using Wrong Comparison in BIT Update

Pitfall: Using maximum instead of minimum in the BIT update operation.

Why it's wrong: We need the leftmost (minimum index) building that satisfies the height requirement. Using maximum would give us the rightmost building instead.

Solution: Ensure the BIT maintains minimum values:

def update(self, index: int, value: int):
    while index <= self.size:
        self.tree[index] = min(self.tree[index], value)  # Use min, not max
        index += index & -index
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

How does quick sort divide the problem into subproblems?


Recommended Readings

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

Load More