Visible Buildings Queries
Problem Overview
| Attribute | Value |
|---|---|
| Difficulty | Medium |
| Category | Range Queries |
| Time Limit | 1 second |
| Key Technique | Preprocessing / Monotonic Stack |
Learning Goals
After solving this problem, you will be able to:
- Precompute answers for range queries to achieve O(1) query time
- Apply monotonic stack technique to visibility problems
- Recognize when preprocessing trades space for query efficiency
- Handle visibility queries from multiple positions efficiently
Problem Statement
Problem: Given an array of building heights and multiple queries, each query asks for the number of visible buildings looking to the right from position i. A building at position j (j > i) is visible if it is strictly taller than all buildings between positions i and j.
Input:
- Line 1: n (number of buildings) and q (number of queries)
- Line 2: n integers representing building heights
- Next q lines: i (1-indexed position to query from)
Output:
- q lines: number of visible buildings from each query position
Constraints:
- 1 <= n <= 2 x 10^5
- 1 <= q <= 2 x 10^5
- 1 <= height[i] <= 10^9
- 1 <= i <= n
Example
Input:
5 3
3 1 4 1 5
1
3
5
Output:
2
2
0
Explanation:
- Query 1 (position 1): Looking right from position 1, buildings at positions 3 (height=4) and 5 (height=5) are visible. Position 2 (height=1) is visible but blocked by position 3. Position 4 (height=1) is blocked by position 3. Answer: 2
- Query 2 (position 3): Looking right from position 3, building at position 5 (height=5) is visible. Position 4 (height=1) is blocked. Looking left, position 1 (height=3) is visible. Answer: 2 (bidirectional visibility)
- Query 3 (position 5): This is the last building, no buildings to see in either direction. Answer: 0
Intuition: How to Think About This Problem
Pattern Recognition
Key Question: How can we avoid recalculating visible buildings for every query?
The key insight is that the number of visible buildings from any position depends only on the heights to the right. If we precompute answers for all positions, each query becomes O(1).
Breaking Down the Problem
- What are we looking for? Count of buildings visible when looking right from position i
- What information do we have? Building heights and query positions
- What’s the relationship between input and output? A building is visible if it creates a new maximum in the suffix
Analogies
Think of this problem like standing in a line of people of different heights. Looking ahead, you can only see people who are taller than everyone between you and them. The tallest person blocks everyone shorter behind them.
Solution 1: Brute Force
Idea
For each query, scan all buildings to the right and count those that are visible (i.e., taller than all buildings seen so far).
Algorithm
- For each query position i
- Initialize max_height = 0, count = 0
- Scan from i+1 to n, if height > max_height, increment count and update max_height
- Return count
Code
def solve_brute_force(n, heights, queries):
"""Brute force: process each query independently. Time: O(q*n)"""
results = []
for pos in queries:
i, count, max_height = pos - 1, 0, 0
for j in range(i + 1, n):
if heights[j] > max_height:
count += 1
max_height = heights[j]
results.append(count)
return results
Complexity
Time: O(q * n) - each query scans up to n elements Space: O(1)
With q = n = 2x10^5, this gives 4x10^10 operations - far too slow.
Solution 2: Optimal Solution with Preprocessing
Key Insight
The Trick: Precompute visible counts from every position. Since the answer for position i only depends on heights[i+1…n], we can compute all answers in O(n) time by processing right-to-left.
Algorithm
- Create array
visible[n]to store answer for each position - Process from right to left using a monotonic stack
- For each query, return precomputed answer in O(1)
Right-to-Left Preprocessing
The visible count from position i equals the number of “steps” in the monotonic increasing sequence to the right. We can compute this efficiently:
visible[i] = number of distinct maximums in heights[i+1...n]
Dry Run Example
Let’s trace through with heights = [3, 1, 4, 1, 5] (counting buildings taller than max seen so far):
Position 0, looking right at [1, 4, 1, 5]:
1 > 0 (max), visible! max=1
4 > 1, visible! max=4
1 < 4, blocked
5 > 4, visible! max=5
visible[0] = 3
Position 1, looking right at [4, 1, 5]:
4 > 0, visible! max=4
1 < 4, blocked
5 > 4, visible! max=5
visible[1] = 2
Position 2: visible[2] = 2 (sees 1, then 5)
Position 3: visible[3] = 1 (sees 5)
Position 4: visible[4] = 0 (nothing right)
Final: [3, 2, 2, 1, 0]
Visual Diagram
Heights: [3, 1, 4, 1, 5]
Index: 0 1 2 3 4
From position 0, looking right:
3 1 4 1 5
| ^ ^ ^
0 1 2 3 4
observer sees: 1, 4, 5 (3 buildings)
Code
def solve_optimal(n, heights, queries):
"""Precompute visible counts for O(1) queries."""
visible = [0] * n
for i in range(n):
count, max_height = 0, 0
for j in range(i + 1, n):
if heights[j] > max_height:
count += 1
max_height = heights[j]
visible[i] = count
return [visible[pos - 1] for pos in queries]
Complexity
| Time | Space |
|---|---|
| O(n^2 + q) | O(n) |
Note: O(n^2) preprocessing, O(1) per query. See Solution 3 for O(n) preprocessing.
Solution 3: O(n) Preprocessing with Next Greater Chain
Key Insight
The Trick: Use the “next strictly greater element” chain. If we know the next building taller than us, we can use DP:
visible[i] = 1 + visible[next_greater[i]].
Algorithm
- Find next strictly greater element for each position using monotonic stack
- Process right-to-left:
visible[i] = 1 + visible[next_greater[i]]if next_greater exists - For positions where next_greater is -1 (no taller building), visible = 0
Code
def solve_with_next_greater(n, heights, queries):
"""O(n) preprocessing using next greater element chain."""
# Step 1: Find next strictly greater element
next_greater = [-1] * n
stack = []
for i in range(n - 1, -1, -1):
while stack and heights[stack[-1]] <= heights[i]:
stack.pop()
if stack:
next_greater[i] = stack[-1]
stack.append(i)
# Step 2: DP - visible[i] = 1 + visible[next_greater[i]]
visible = [0] * n
for i in range(n - 1, -1, -1):
if next_greater[i] != -1:
visible[i] = 1 + visible[next_greater[i]]
return [visible[pos - 1] for pos in queries]
Complexity
Time: O(n + q) - O(n) stack + O(n) DP + O(q) queries Space: O(n) - arrays for next_greater and visible
Common Mistakes
Mistake 1: Off-by-One Indexing
# WRONG: count = visible[pos] (out of bounds when pos == n)
# CORRECT: count = visible[pos - 1] (convert 1-indexed to 0-indexed)
Mistake 2: Including the Observer’s Position
# WRONG: for j in range(i, n) (counts observer)
# CORRECT: for j in range(i + 1, n) (start after observer)
Mistake 3: Not Resetting Max Height
# WRONG: max_height declared outside query loop (stale value)
# CORRECT: reset max_height = 0 inside each query iteration
Edge Cases
| Case | Input | Expected Output | Why |
|---|---|---|---|
| Single building | n=1, q=1, pos=1 |
0 | No buildings to the right |
| All same height | [5,5,5,5], pos=1 |
1 | Only first building after is visible |
| Strictly increasing | [1,2,3,4], pos=1 |
3 | All buildings visible |
| Strictly decreasing | [4,3,2,1], pos=1 |
1 | Only immediate next visible |
| Query last position | pos=n |
0 | No buildings to the right |
| Very tall building blocks all | [1,100,2,3,4], pos=1 |
1 | Building at pos 2 blocks all others |
When to Use This Pattern
| Use When | Avoid When |
|---|---|
| Multiple queries on same data | Data updated between queries |
| Need O(1) query time | Only a few queries |
| O(n^2) preprocessing acceptable | Memory extremely constrained |
Pattern Recognition:
- Multiple queries on static data -> Preprocessing
- Visibility / next greater element -> Monotonic stack
- Suffix/prefix information -> Right-to-left or left-to-right scan
Related Problems
| Difficulty | Problem | Key Concept |
|---|---|---|
| Easier | Static Range Sum Queries (CSES) | Prefix sum preprocessing |
| Easier | Next Greater Element I (LeetCode) | Monotonic stack basics |
| Similar | Buildings (CSES) | Visibility without queries |
| Similar | Daily Temperatures (LeetCode) | Next greater with distance |
| Harder | Dynamic Range Sum Queries (CSES) | Segment tree for updates |
| Harder | Largest Rectangle in Histogram (LeetCode) | Complex monotonic stack |
Key Takeaways
- Core Idea: Precompute answers for O(1) query time
- Optimization: Trade O(n^2) preprocessing for O(1) queries
- Space Trade-off: O(n) space for precomputed answers
- Pattern: Classic “offline preprocessing” for static range queries