Facebook Pixel

2282. Number of People That Can Be Seen in a Grid 🔒

Problem Description

You have an m x n grid where each cell contains a positive integer representing the height of a person standing at that position. The goal is to determine how many people each person in the grid can see.

A person at position (row1, col1) can see another person at position (row2, col2) if two conditions are met:

  1. Direction constraint: The second person must be either:

    • In the same row to the right: row1 == row2 and col1 < col2, OR
    • In the same column below: row1 < row2 and col1 == col2
  2. Height constraint: All people between these two positions must be shorter than both of them. This means if person A can see person B, everyone standing between them must have a height less than both heights[row1][col1] and heights[row2][col2].

You need to return an m x n matrix where each cell answer[i][j] contains the count of people that the person at position (i, j) can see.

For example, if a person is at position (1, 1), they can only look:

  • To the right along row 1 (positions (1, 2), (1, 3), etc.)
  • Downward along column 1 (positions (2, 1), (3, 1), etc.)

They cannot look diagonally, to the left, or upward. The number of visible people depends on the heights - taller people in between can block the view of shorter people behind them.

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

Intuition

The key insight is that a person can only see others who form a "visibility sequence" - where each visible person must be taller than all the people between them and the observer.

Think about it from one person's perspective: when looking in a direction (right or down), the people they can see must have increasing heights. Why? Because if person B is shorter than person A, and person A is between the observer and person B, then person A would block the view of person B.

This naturally leads us to think about monotonic sequences. When we look from a position, the visible people form a sequence where each subsequent visible person must be taller than the previous ones (otherwise they'd be blocked).

Now, how do we efficiently find these visible people? If we process people from far to near (reverse order), we can maintain a stack of "potentially visible" people. As we move closer to the observer:

  • If we encounter someone taller than people in our stack, those shorter people become visible to our current person (they can see over them)
  • We remove people from the stack who are shorter than the current person (they're now blocked)
  • The remaining people in the stack who are taller than or equal to the current person might still be visible

This is the classic monotonic stack pattern! We maintain a stack that's monotonically decreasing (from bottom to top) as we traverse in reverse. Each person can see:

  1. All people they can "see over" (those popped from the stack because they're shorter)
  2. At most one person who is taller or equal (the first one remaining in the stack)

Since people can only look right or down, we apply this algorithm:

  • Once for each row (to count people visible to the right)
  • Once for each column (to count people visible below)
  • Sum both counts for each position to get the total number of visible people

Learn more about Stack and Monotonic Stack patterns.

Solution Approach

The solution implements a monotonic stack approach by creating a helper function f(nums) that counts visible people in a single direction (either horizontal or vertical).

Helper Function f(nums):

The function processes a 1D array from right to left (far to near) and uses a monotonic stack to track visible people:

  1. Initialize: Create an empty stack stk and a result array ans of zeros
  2. Traverse in reverse (from index n-1 to 0):
    • Count shorter people: While the stack has elements and stk[-1] < nums[i], increment ans[i] and pop from stack. These are people the current person can see over.
    • Check for taller person: If the stack still has elements, increment ans[i] by 1. This represents the first person taller than or equal to the current person.
    • Remove equal heights: While the stack has elements and stk[-1] == nums[i], pop them. People of equal height block each other.
    • Add current person: Push nums[i] onto the stack for future comparisons.

Main Algorithm:

  1. Process rows (looking right):

    • Apply f(row) to each row in the heights matrix
    • This gives us the count of people visible to the right for each position
  2. Process columns (looking down):

    • For each column j, extract the column as [heights[i][j] for i in range(m)]
    • Apply f() to this column array to get visibility counts looking down
    • Add these counts to the corresponding positions in the answer matrix
  3. Combine results: The final answer at each position is the sum of:

    • People visible to the right (from row processing)
    • People visible below (from column processing)

Stack Invariant: The stack maintains heights in non-increasing order from bottom to top. When we encounter a new person:

  • Shorter people in the stack are visible and get removed
  • The first taller/equal person (if exists) is also visible but remains
  • Equal height people get removed to prevent double counting

Time Complexity: O(m × n) - each element is pushed and popped from the stack at most once for row processing and once for column processing.

Space Complexity: O(max(m, n)) - the stack can contain at most one row or column worth of elements at a time.

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 to illustrate the solution approach.

Consider a 3×3 grid of heights:

heights = [[4, 2, 3],
           [1, 5, 2],
           [3, 1, 4]]

We need to count how many people each person can see (looking only right or down).

Step 1: Process Rows (Looking Right)

For each row, we apply the helper function f():

Row 0: [4, 2, 3]

  • Process from right to left using a stack:
    • i=2 (height=3): stack=[], ans[2]=0, push 3 → stack=[3]
    • i=1 (height=2): stack=[3], 2<3 so ans[1]++, pop 3, stack=[], push 2 → stack=[2]
    • i=0 (height=4): stack=[2], 4>2 so ans[0]++, pop 2, stack=[], push 4 → stack=[4]
  • Result for row 0: [1, 1, 0]

Row 1: [1, 5, 2]

  • Process from right to left:
    • i=2 (height=2): stack=[], ans[2]=0, push 2 → stack=[2]
    • i=1 (height=5): stack=[2], 5>2 so ans[1]++, pop 2, stack=[], push 5 → stack=[5]
    • i=0 (height=1): stack=[5], 1<5 so stack stays, ans[0]++ for seeing 5 → ans[0]=1
  • Result for row 1: [1, 1, 0]

Row 2: [3, 1, 4]

  • Process from right to left:
    • i=2 (height=4): stack=[], ans[2]=0, push 4 → stack=[4]
    • i=1 (height=1): stack=[4], 1<4 so stack stays, ans[1]++ for seeing 4 → ans[1]=1
    • i=0 (height=3): stack=[4,1], 3>1 so ans[0]++, pop 1, stack=[4], 3<4 so ans[0]++ → ans[0]=2
  • Result for row 2: [2, 1, 0]

After row processing:

answer = [[1, 1, 0],
          [1, 1, 0],
          [2, 1, 0]]

Step 2: Process Columns (Looking Down)

Column 0: [4, 1, 3]

  • Process from bottom to top:
    • i=2 (height=3): stack=[], ans[2]=0, push 3 → stack=[3]
    • i=1 (height=1): stack=[3], 1<3 so stack stays, ans[1]++ for seeing 3 → ans[1]=1
    • i=0 (height=4): stack=[3,1], 4>1 so ans[0]++, pop 1, stack=[3], 4>3 so ans[0]++, pop 3 → ans[0]=2
  • Add to column 0: [2, 1, 0]

Column 1: [2, 5, 1]

  • Process from bottom to top:
    • i=2 (height=1): stack=[], ans[2]=0, push 1 → stack=[1]
    • i=1 (height=5): stack=[1], 5>1 so ans[1]++, pop 1, stack=[], push 5 → stack=[5]
    • i=0 (height=2): stack=[5], 2<5 so stack stays, ans[0]++ for seeing 5 → ans[0]=1
  • Add to column 1: [1, 1, 0]

Column 2: [3, 2, 4]

  • Process from bottom to top:
    • i=2 (height=4): stack=[], ans[2]=0, push 4 → stack=[4]
    • i=1 (height=2): stack=[4], 2<4 so stack stays, ans[1]++ for seeing 4 → ans[1]=1
    • i=0 (height=3): stack=[4,2], 3>2 so ans[0]++, pop 2, stack=[4], 3<4 so ans[0]++ → ans[0]=2
  • Add to column 2: [2, 1, 0]

Step 3: Combine Results

Final answer (sum of row and column visibility):

answer = [[1+2, 1+1, 0+2],     [[3, 2, 2],
          [1+1, 1+1, 0+1],  =    [2, 2, 1],
          [2+0, 1+0, 0+0]]       [2, 1, 0]]

Each cell shows the total number of people that person can see by looking right and down.

Solution Implementation

1from typing import List
2
3
4class Solution:
5    def seePeople(self, heights: List[List[int]]) -> List[List[int]]:
6        """
7        Count the number of people each person can see to their right and below.
8        A person can see another person if they are taller than all people between them,
9        or if they are the first person of equal or greater height.
10      
11        Args:
12            heights: 2D grid where heights[i][j] represents the height of person at (i, j)
13      
14        Returns:
15            2D grid where result[i][j] is the count of people (i, j) can see
16        """
17      
18        def count_visible_people(heights_list: List[int]) -> List[int]:
19            """
20            Count visible people to the right for each person in a single row/column.
21            Uses a monotonic stack approach to efficiently count visible people.
22          
23            Args:
24                heights_list: List of heights in a row or column
25          
26            Returns:
27                List where result[i] is the count of people position i can see to the right
28            """
29            n = len(heights_list)
30            stack = []  # Monotonic stack to track heights
31            visible_count = [0] * n
32          
33            # Process from right to left to count people visible to the right
34            for i in range(n - 1, -1, -1):
35                current_height = heights_list[i]
36              
37                # Count and remove all shorter people that current person can see
38                while stack and stack[-1] < current_height:
39                    visible_count[i] += 1
40                    stack.pop()
41              
42                # If there's a taller or equal person, current person can see them
43                if stack:
44                    visible_count[i] += 1
45              
46                # Remove people of same height (they would be blocked)
47                while stack and stack[-1] == current_height:
48                    stack.pop()
49              
50                # Add current person's height to stack
51                stack.append(current_height)
52          
53            return visible_count
54      
55        # Calculate visible people to the right for each row
56        result = [count_visible_people(row) for row in heights]
57      
58        # Get dimensions
59        num_rows, num_cols = len(heights), len(heights[0])
60      
61        # Calculate visible people below for each column and add to result
62        for col in range(num_cols):
63            # Extract column heights
64            column_heights = [heights[row][col] for row in range(num_rows)]
65            # Count visible people going down
66            visible_below = count_visible_people(column_heights)
67            # Add to existing count
68            for row in range(num_rows):
69                result[row][col] += visible_below[row]
70      
71        return result
72
1class Solution {
2    /**
3     * Calculates the number of people each person can see to their right and down.
4     * A person can see another person if there's no one taller blocking the view.
5     * 
6     * @param heights 2D array where heights[i][j] represents the height of person at position (i, j)
7     * @return 2D array where result[i][j] is the count of people that person at (i, j) can see
8     */
9    public int[][] seePeople(int[][] heights) {
10        int numRows = heights.length;
11        int numCols = heights[0].length;
12        int[][] result = new int[numRows][numCols];
13      
14        // Calculate visible people to the right for each row
15        for (int row = 0; row < numRows; row++) {
16            result[row] = countVisiblePeople(heights[row]);
17        }
18      
19        // Calculate visible people downward for each column and add to result
20        for (int col = 0; col < numCols; col++) {
21            // Extract column values into a separate array
22            int[] columnHeights = new int[numRows];
23            for (int row = 0; row < numRows; row++) {
24                columnHeights[row] = heights[row][col];
25            }
26          
27            // Count visible people in this column
28            int[] visibleInColumn = countVisiblePeople(columnHeights);
29          
30            // Add column visibility counts to the result
31            for (int row = 0; row < numRows; row++) {
32                result[row][col] += visibleInColumn[row];
33            }
34        }
35      
36        return result;
37    }
38  
39    /**
40     * Helper method to count visible people in a 1D array using monotonic stack.
41     * For each position, counts how many people to the right are visible.
42     * 
43     * @param heights 1D array of heights
44     * @return array where result[i] is the count of visible people from position i
45     */
46    private int[] countVisiblePeople(int[] heights) {
47        int length = heights.length;
48        int[] visibleCount = new int[length];
49        Deque<Integer> monotonicStack = new ArrayDeque<>();
50      
51        // Process from right to left
52        for (int i = length - 1; i >= 0; i--) {
53            // Pop all people shorter than current person (they are visible)
54            while (!monotonicStack.isEmpty() && monotonicStack.peek() < heights[i]) {
55                monotonicStack.pop();
56                visibleCount[i]++;
57            }
58          
59            // If stack is not empty, the person at top is visible (first taller or equal person)
60            if (!monotonicStack.isEmpty()) {
61                visibleCount[i]++;
62            }
63          
64            // Remove people with same height to avoid duplicates
65            while (!monotonicStack.isEmpty() && monotonicStack.peek() == heights[i]) {
66                monotonicStack.pop();
67            }
68          
69            // Add current person's height to stack
70            monotonicStack.push(heights[i]);
71        }
72      
73        return visibleCount;
74    }
75}
76
1class Solution {
2public:
3    vector<vector<int>> seePeople(vector<vector<int>>& heights) {
4        int m = heights.size();
5        int n = heights[0].size();
6      
7        // Lambda function to count visible people in a single direction (row or column)
8        // Uses monotonic stack to find how many people each person can see
9        auto countVisiblePeople = [](vector<int>& heights) -> vector<int> {
10            int size = heights.size();
11            vector<int> visibleCount(size, 0);
12            stack<int> monotonicStack;  // Stores heights in decreasing order
13          
14            // Process from right to left (or bottom to top)
15            for (int i = size - 1; i >= 0; --i) {
16                // Count and remove all people shorter than current person
17                while (!monotonicStack.empty() && monotonicStack.top() < heights[i]) {
18                    ++visibleCount[i];
19                    monotonicStack.pop();
20                }
21              
22                // If stack is not empty, current person can see one taller person
23                if (!monotonicStack.empty()) {
24                    ++visibleCount[i];
25                }
26              
27                // Remove people with same height (they block each other)
28                while (!monotonicStack.empty() && monotonicStack.top() == heights[i]) {
29                    monotonicStack.pop();
30                }
31              
32                // Add current person's height to stack
33                monotonicStack.push(heights[i]);
34            }
35          
36            return visibleCount;
37        };
38      
39        // Initialize result matrix
40        vector<vector<int>> result;
41      
42        // Process each row (looking to the right)
43        for (auto& row : heights) {
44            result.push_back(countVisiblePeople(row));
45        }
46      
47        // Process each column (looking down)
48        for (int col = 0; col < n; ++col) {
49            // Extract column values
50            vector<int> column;
51            for (int row = 0; row < m; ++row) {
52                column.push_back(heights[row][col]);
53            }
54          
55            // Count visible people looking down
56            vector<int> columnVisibleCount = countVisiblePeople(column);
57          
58            // Add column counts to the result
59            for (int row = 0; row < m; ++row) {
60                result[row][col] += columnVisibleCount[row];
61            }
62        }
63      
64        return result;
65    }
66};
67
1/**
2 * Counts the number of people each person can see in a 2D grid.
3 * Each person can see others to their right (in the same row) and below (in the same column)
4 * if those people are shorter or equal height, until blocked by someone taller.
5 * 
6 * @param heights - 2D array where heights[i][j] represents the height of person at position (i, j)
7 * @returns 2D array where result[i][j] is the count of people that person at (i, j) can see
8 */
9function seePeople(heights: number[][]): number[][] {
10    /**
11     * Helper function to count visible people in a single direction (row or column).
12     * Uses a monotonic stack to efficiently track visibility.
13     * 
14     * @param nums - Array of heights in a single row or column
15     * @returns Array where each element represents the count of visible people from that position
16     */
17    const countVisiblePeople = (nums: number[]): number[] => {
18        const length = nums.length;
19        const visibilityCount: number[] = new Array(length).fill(0);
20        const monotonicStack: number[] = [];
21      
22        // Process from right to left (or bottom to top)
23        for (let i = length - 1; i >= 0; i--) {
24            // Pop all people shorter than current person (they are visible)
25            while (monotonicStack.length > 0 && monotonicStack.at(-1)! < nums[i]) {
26                monotonicStack.pop();
27                visibilityCount[i]++;
28            }
29          
30            // If stack still has elements, the next person is taller (also visible)
31            if (monotonicStack.length > 0) {
32                visibilityCount[i]++;
33            }
34          
35            // Remove people of same height (they won't be visible to people on the left)
36            while (monotonicStack.length > 0 && monotonicStack.at(-1) === nums[i]) {
37                monotonicStack.pop();
38            }
39          
40            // Add current person's height to stack
41            monotonicStack.push(nums[i]);
42        }
43      
44        return visibilityCount;
45    };
46  
47    const result: number[][] = [];
48  
49    // Process each row to count people visible to the right
50    for (const row of heights) {
51        result.push(countVisiblePeople(row));
52    }
53  
54    // Process each column to count people visible below
55    const numColumns = heights[0].length;
56    for (let columnIndex = 0; columnIndex < numColumns; columnIndex++) {
57        // Extract the current column
58        const column: number[] = [];
59        for (const row of heights) {
60            column.push(row[columnIndex]);
61        }
62      
63        // Count visible people in this column
64        const columnVisibility = countVisiblePeople(column);
65      
66        // Add column visibility counts to the result
67        for (let rowIndex = 0; rowIndex < result.length; rowIndex++) {
68            result[rowIndex][columnIndex] += columnVisibility[rowIndex];
69        }
70    }
71  
72    return result;
73}
74

Time and Space Complexity

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

The analysis breaks down as follows:

  • The helper function f(nums) processes an array of length k in O(k) time, as each element is pushed and popped from the stack at most once.
  • Processing all rows: We call f(row) for each of the m rows, where each row has n elements. This takes O(m * n) time total.
  • Processing all columns: We call f([heights[i][j] for i in range(m)]) for each of the n columns, where each column has m elements. Creating the column array takes O(m) time, and processing it takes O(m) time. This is done n times, resulting in O(n * m) time.
  • The final loop to add column results takes O(m * n) time.
  • Total: O(m * n) + O(n * m) + O(m * n) = O(m * n * 2) = O(m * n)

However, considering the worst-case scenario where we need to traverse through both dimensions completely for each cell, and the stack operations within function f, the more precise complexity is O(m * n * (m + n)) when accounting for the maximum possible stack operations across all rows and columns.

Space Complexity: O(m + n)

The space analysis:

  • The stack in function f can hold at most O(k) elements where k is the length of the input array.
  • The ans array for function f uses O(k) space.
  • When processing rows, the maximum stack size is O(n).
  • When processing columns, we create a temporary array of size O(m) and the stack can hold at most O(m) elements.
  • The output array ans is O(m * n) but this is not counted as auxiliary space since it's the required output.
  • Maximum auxiliary space used at any point: O(max(m, n)) which simplifies to O(m + n).

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

Common Pitfalls

1. Incorrect Handling of Equal Heights

The Pitfall: A common mistake is treating people of equal height inconsistently. Developers often forget that if person A (height H) can see person B (also height H), then person A cannot see anyone beyond person B, even if those people are taller. This is because person B blocks the view completely.

Incorrect Implementation:

# WRONG: Not removing equal heights from stack
while stack and stack[-1] < current_height:
    visible_count[i] += 1
    stack.pop()
if stack:
    visible_count[i] += 1
# Missing: removal of equal heights
stack.append(current_height)

Why It's Wrong: This keeps equal heights in the stack, which can lead to:

  • Double counting when multiple people of the same height exist
  • Incorrect visibility calculations for subsequent people

Correct Solution:

# Remove all shorter people
while stack and stack[-1] < current_height:
    visible_count[i] += 1
    stack.pop()

# Count the first taller/equal person
if stack:
    visible_count[i] += 1

# IMPORTANT: Remove all equal heights to prevent blocking issues
while stack and stack[-1] == current_height:
    stack.pop()

stack.append(current_height)

2. Processing Direction Confusion

The Pitfall: Processing the array in the wrong direction (left-to-right instead of right-to-left) when building the visibility count.

Incorrect Implementation:

# WRONG: Processing from left to right
for i in range(n):  # Should be range(n-1, -1, -1)
    current_height = heights_list[i]
    # ... rest of logic

Why It's Wrong: The stack approach requires processing from the far end (right/bottom) toward the near end (left/top) because:

  • We need to know who is visible from each position looking forward
  • The stack maintains people that haven't been "seen over" yet
  • Processing backwards allows us to build this information incrementally

Correct Solution: Always process from right-to-left for horizontal visibility and bottom-to-top for vertical visibility:

for i in range(n - 1, -1, -1):  # Correct direction
    current_height = heights_list[i]
    # ... rest of logic

3. Misunderstanding the Visibility Rules

The Pitfall: Assuming that a person can see ALL people shorter than them in their line of sight, rather than understanding that the first person of equal or greater height blocks all visibility beyond them.

Example Scenario:

Heights: [4, 2, 1, 5, 3]
Person at index 0 (height 4) can see:
- Person at index 1 (height 2) ✓
- Person at index 2 (height 1) ✓
- Person at index 3 (height 5) ✓
- Person at index 4 (height 3) ✗ (blocked by person at index 3)

Correct Understanding: Once you encounter someone taller than or equal to the current person, they block all visibility beyond that point, regardless of the heights of people further away.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Consider the classic dynamic programming of fibonacci numbers, what is the recurrence relation?


Recommended Readings

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

Load More