Block Placement Queries
HardPrompt
There exists an infinite number line, with its origin at 0 and extending towards the positive x-axis.
You are given a 2D array queries, which contains two types of queries:
- For a query of type 1,
queries[i] = [1, x]. Build an obstacle at distancexfrom the origin. It is guaranteed that there is no obstacle at distancexwhen the query is asked. - For a query of type 2,
queries[i] = [2, x, sz]. Check if it is possible to place a block of sizeszanywhere in the range[0, x]on the line, such that the block entirely lies in the range[0, x]. A block cannot be placed if it intersects with any obstacle, but it may touch it. Note that you do not actually place the block. Queries are separate.
Return a boolean array results, where results[i] is true if you can place the block specified in the ith query of type 2, and false otherwise.
Example 1:
Input: queries = [[1,2],[2,3,3],[2,3,1],[2,2,2]]
Output: [false,true,true]
Explanation:

For query 0, place an obstacle at x = 2. A block of size at most 2 can be placed before x = 3.
Example 2:
Input: queries = [[1,7],[2,7,6],[1,2],[2,7,5],[2,7,6]]
Output: [true,true,false]
Explanation:

- Place an obstacle at
x = 7for query 0. A block of size at most 7 can be placed beforex = 7. - Place an obstacle at
x = 2for query 2. Now, a block of size at most 5 can be placed beforex = 7, and a block of size at most 2 beforex = 2.
Constraints:
1 <= queries.length <= 15 * 1042 <= queries[i].length <= 31 <= queries[i][0] <= 21 <= x, sz <= min(5 * 104, 3 * queries.length)- The input is generated such that for queries of type 1, no obstacle exists at distance
xwhen the query is asked. - The input is generated such that there is at least one query of type 2.
Approaches
3 approaches with complexity analysis and trade-offs.
This approach directly simulates the problem statement. For each type 1 query, we record the new obstacle's position. For each type 2 query, we gather all obstacles within the query's range [0, x], sort them, and then iterate through the sorted positions to find the largest available gap. This largest gap is then compared with the required block size.
Algorithm
- Maintain a simple list of all obstacle positions.
- For a type 1 query
[1, pos], addposto the list of obstacles. - For a type 2 query
[2, x, sz]:- Create a temporary list of relevant points, initially containing
0. - Iterate through all obstacles placed so far. Add any obstacle with position
<= xto this temporary list. - Sort the temporary list.
- Iterate through the sorted list to find the maximum gap between consecutive points. Let the sorted points be
p_0, p_1, ..., p_k. The gaps arep_1 - p_0, p_2 - p_1, ..., p_k - p_{k-1}. - Also, calculate the gap from the last point
p_ktox, which isx - p_k. - The largest possible block size is the maximum of all these calculated gaps.
- Compare this maximum size with
szto determine if the block can be placed.
- Create a temporary list of relevant points, initially containing
Walkthrough
We use a standard List to store the positions of all obstacles as they are added. When a type 1 query [1, pos] arrives, we simply append pos to our list.
For a type 2 query [2, x, sz], we need to determine the largest contiguous empty space within the [0, x] interval. To do this, we first construct a new list containing 0 and all obstacle positions from our main list that are less than or equal to x. We then sort this new list to get an ordered set of points that define the boundaries of available segments. The largest gap is found by calculating the difference between each pair of consecutive points in the sorted list, and also the difference between x and the last point. The maximum of these values gives the size of the largest block that can be placed. If this size is at least sz, the query result is true.
import java.util.ArrayList;import java.util.Collections;import java.util.List; class Solution { public List<Boolean> getResults(int[][] queries) { List<Integer> obstacles = new ArrayList<>(); List<Boolean> results = new ArrayList<>(); for (int[] query : queries) { if (query[0] == 1) { obstacles.add(query[1]); } else { int x = query[1]; int sz = query[2]; List<Integer> currentPoints = new ArrayList<>(); currentPoints.add(0); for (int obs : obstacles) { if (obs <= x) { currentPoints.add(obs); } } Collections.sort(currentPoints); int maxGap = 0; for (int i = 1; i < currentPoints.size(); i++) { maxGap = Math.max(maxGap, currentPoints.get(i) - currentPoints.get(i - 1)); } maxGap = Math.max(maxGap, x - currentPoints.get(currentPoints.size() - 1)); results.add(maxGap >= sz); } } return results; }}Complexity
Time
O(Q_1 + Q_2 * Q_1 * log(Q_1)), where Q_1 and Q_2 are the counts of type 1 and type 2 queries, respectively. In the worst case where Q_1 ≈ Q and Q_2 ≈ Q, this becomes O(Q^2 log Q).
Space
O(Q_1), where Q_1 is the number of type 1 queries, to store the obstacle positions.
Trade-offs
Pros
Simple to understand and implement.
Correct for small inputs.
Cons
Extremely inefficient due to re-filtering and re-sorting obstacles for every type 2 query.
Will result in a 'Time Limit Exceeded' error for larger test cases.
This approach improves upon the brute-force method by using a sorted data structure, such as a TreeSet in Java, to maintain the obstacle positions. This avoids the costly step of re-sorting for every query. Adding an obstacle is efficient. However, finding the maximum gap for a type 2 query still requires iterating through all relevant obstacles.
Algorithm
- Use a
TreeSetto store obstacle positions, which keeps them sorted automatically. - For a type 1 query
[1, pos], addposto theTreeSet. - For a type 2 query
[2, x, sz]:- Initialize
max_gap = 0andlast_pos = 0. - Iterate through the obstacles in the
TreeSetthat are less than or equal tox. This can be done efficiently usingTreeSet.headSet(x, true). - For each obstacle
obs, calculate the gapobs - last_posand updatemax_gap. Then, setlast_pos = obs. - After the loop, calculate the final gap from the last obstacle to
x, which isx - last_pos, and updatemax_gap. - Compare
max_gapwithszfor the result.
- Initialize
Walkthrough
By storing obstacle positions in a TreeSet, we ensure they are always kept in sorted order. A type 1 query [1, pos] becomes a simple add operation on the TreeSet, which takes logarithmic time.
For a type 2 query [2, x, sz], we can find the maximum gap more efficiently than the brute-force approach. We iterate through the obstacles in the TreeSet that are at or before position x. We can get a view of this subset of obstacles using headSet(x, true). We traverse these obstacles, keeping track of the previous position (last_pos, initialized to 0) to calculate the gap size. The maximum gap found during this iteration is recorded. Finally, we account for the gap between the last relevant obstacle and x. The overall maximum gap is then compared to sz.
import java.util.ArrayList;import java.util.List;import java.util.TreeSet; class Solution { public List<Boolean> getResults(int[][] queries) { TreeSet<Integer> obstacles = new TreeSet<>(); List<Boolean> results = new ArrayList<>(); for (int[] query : queries) { if (query[0] == 1) { obstacles.add(query[1]); } else { int x = query[1]; int sz = query[2]; int maxGap = 0; int lastPos = 0; // Add 0 to the set for calculation, but it's implicitly handled by lastPos=0 for (int obs : obstacles.headSet(x, true)) { maxGap = Math.max(maxGap, obs - lastPos); lastPos = obs; } maxGap = Math.max(maxGap, x - lastPos); results.add(maxGap >= sz); } } return results; }}Complexity
Time
O(Q_1 * log(Q_1) + Q_2 * Q_1). Adding an obstacle takes O(log Q_1). A type 2 query iterates through up to O(Q_1) obstacles. In the worst case, this is O(Q^2).
Space
O(Q_1) to store the obstacles in the `TreeSet`.
Trade-offs
Pros
Faster than the pure brute-force approach by avoiding repeated sorting.
Relatively easy to implement using standard library data structures.
Cons
While better than brute-force, it still requires a linear scan over a subset of obstacles for each type 2 query, making it too slow for the given constraints.
The complexity is quadratic in the worst-case scenario.
This is a highly efficient approach that leverages a segment tree to quickly query for the maximum gap size. The core idea is to maintain the lengths of all gaps between consecutive obstacles in a data structure that supports fast range maximum queries and point updates. A segment tree is perfectly suited for this. By combining it with a TreeSet to keep track of obstacle locations, we can process both types of queries in logarithmic time.
Algorithm
- Use a
Segment Treedata structure built over the range of possible coordinates[0, M], whereMis the maximum coordinate value from the input. - The segment tree will store the maximum gap length. Specifically, at leaf index
p, we store the length of the gap that ends at positionp. - Use a
TreeSetto maintain the sorted positions of obstacles. - Type 1 Query
[1, p]: When adding an obstaclep, it splits an existing gap. Find its neighborsprev_obsandnext_obsfrom theTreeSet. The gap(prev_obs, next_obs)is replaced by(prev_obs, p)and(p, next_obs). Update the segment tree: set the value at indexptop - prev_obsand the value at indexnext_obstonext_obs - p. Then addpto theTreeSet. - Type 2 Query
[2, x, sz]: The maximum block size is the larger of two values:- The maximum gap between any two consecutive obstacles both located at or before
x. This is found by querying the segment tree for the maximum value in the range[0, x]. - The gap from the last obstacle before
xtoxitself. This is found usingTreeSet.floor(x).
- The maximum gap between any two consecutive obstacles both located at or before
- Compare the result with
sz.
Walkthrough
To solve this problem efficiently, we need to quickly answer the question: 'What is the largest available space in [0, x]?'. This space is determined by the maximum of two quantities: 1) the largest gap between two consecutive obstacles within [0, x], and 2) the gap from the last obstacle in [0, x] to x.
A TreeSet helps us find the last obstacle and its neighbors efficiently. A segment tree helps us find the maximum internal gap. We build the segment tree over the coordinate space. Each leaf i of the tree stores the length of the gap that ends at position i. An internal node stores the maximum gap length in its corresponding range.
When an obstacle at p is added (Type 1), it splits a gap (prev, next). We update our segment tree by changing the gap lengths associated with endpoints p and next. This is a point update operation, which is O(log M).
When we check for placing a block (Type 2), we query the segment tree for the maximum value in [0, x] to get the largest internal gap. This is a range query, also O(log M). We also find the gap to x using the TreeSet in O(log Q_1). The maximum of these two values gives us our answer.
import java.util.ArrayList;import java.util.List;import java.util.TreeSet; class SegmentTree { private int[] tree; private int n; public SegmentTree(int size) { this.n = size; this.tree = new int[4 * n]; } public void update(int node, int start, int end, int idx, int val) { if (start == end) { tree[node] = val; return; } int mid = start + (end - start) / 2; if (start <= idx && idx <= mid) { update(2 * node + 1, start, mid, idx, val); } else { update(2 * node + 2, mid + 1, end, idx, val); } tree[node] = Math.max(tree[2 * node + 1], tree[2 * node + 2]); } public int query(int node, int start, int end, int l, int r) { if (r < start || end < l || l > r) { return 0; } if (l <= start && end <= r) { return tree[node]; } int mid = start + (end - start) / 2; int p1 = query(2 * node + 1, start, mid, l, r); int p2 = query(2 * node + 2, mid + 1, end, l, r); return Math.max(p1, p2); }} class Solution { public List<Boolean> getResults(int[][] queries) { int maxCoord = 0; for (int[] q : queries) { maxCoord = Math.max(maxCoord, q[1]); } maxCoord++; SegmentTree st = new SegmentTree(maxCoord); TreeSet<Integer> obstacles = new TreeSet<>(); obstacles.add(0); List<Boolean> results = new ArrayList<>(); for (int[] query : queries) { if (query[0] == 1) { int p = query[1]; Integer prev = obstacles.floor(p); Integer next = obstacles.ceiling(p); if (next != null) { st.update(0, 0, maxCoord - 1, next, next - p); } st.update(0, 0, maxCoord - 1, p, p - prev); obstacles.add(p); } else { int x = query[1]; int sz = query[2]; int maxInternalGap = st.query(0, 0, maxCoord - 1, 0, x); Integer lastObs = obstacles.floor(x); int gapToX = x - lastObs; results.add(Math.max(maxInternalGap, gapToX) >= sz); } } return results; }}Complexity
Time
O(Q * (log Q + log M)), where Q is the number of queries and M is the maximum coordinate. Each query involves a few `TreeSet` (O(log Q)) and segment tree (O(log M)) operations.
Space
O(Q_1 + M), where Q_1 is the number of obstacles and M is the maximum coordinate value. O(Q_1) for the `TreeSet` and O(M) for the segment tree.
Trade-offs
Pros
Highly efficient, with logarithmic time complexity per query.
Scales well for large inputs and passes within the time limits.
Provides a general framework for solving problems involving dynamic gap management.
Cons
Significantly more complex to implement than the previous approaches.
Requires a good understanding of segment trees.
Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.