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:
-
Direction constraint: The second person must be either:
- In the same row to the right:
row1 == row2
andcol1 < col2
, OR - In the same column below:
row1 < row2
andcol1 == col2
- In the same row to the right:
-
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]
andheights[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.
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:
- All people they can "see over" (those popped from the stack because they're shorter)
- 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:
- Initialize: Create an empty stack
stk
and a result arrayans
of zeros - Traverse in reverse (from index
n-1
to0
):- Count shorter people: While the stack has elements and
stk[-1] < nums[i]
, incrementans[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.
- Count shorter people: While the stack has elements and
Main Algorithm:
-
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
- Apply
-
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
- For each column
-
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 EvaluatorExample 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 lengthk
inO(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 them
rows, where each row hasn
elements. This takesO(m * n)
time total. - Processing all columns: We call
f([heights[i][j] for i in range(m)])
for each of then
columns, where each column hasm
elements. Creating the column array takesO(m)
time, and processing it takesO(m)
time. This is donen
times, resulting inO(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 mostO(k)
elements wherek
is the length of the input array. - The
ans
array for functionf
usesO(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 mostO(m)
elements. - The output array
ans
isO(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 toO(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.
Consider the classic dynamic programming of fibonacci numbers, what is the recurrence relation?
Recommended Readings
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top you
Monotonic Stack Deque Intro If you'd prefer a video div class responsive iframe iframe src https www youtube com embed Dq_ObZwTY_Q title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div The word monotonic means a list or
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!