2282. Number of People That Can Be Seen in a Grid
Problem Description
In this problem, we have a 2D array called heights
that represents the heights of individuals standing in a grid. The grid has m
rows and n
columns, and heights[i][j]
is the height of the person at position (i, j)
.
A person at position (row1, col1)
can see another person at position (row2, col2)
if two conditions are met:
- The person at
(row2, col2)
is either to the right or to the below of the person at(row1, col1)
. Specifically, this means eitherrow1
is the same asrow2
andcol1 < col2
, orrow1 < row2
andcol1
is the same ascol2
. - Everyone standing in the direct line of sight (in between these two positions horizontally or vertically) is shorter than both the observing person and the observed person.
The goal is to return a new m x n
2D array named answer
, where answer[i][j]
contains the number of people that the person at position (i, j)
can see based on the above rules.
Intuition
The solution to this problem can be approached using stacks to efficiently keep track of the people in line of sight. We iterate through each row and each column to count how many people a person at (i, j)
can see to their right and below respectively.
For each row, we start from the rightmost position and move left. We maintain a stack that keeps track of the heights of the people who are in direct line of sight and not blocked by anyone taller so far. If a new person is taller than the person at the top of the stack, then we keep popping people off the stack until we find someone taller or the stack is empty. The number of people popped is the number of people this new person can see to their right. If there is still someone on the stack, it means the new person can also see them, so we add 1 more. If there are people with the same height, we remove them since they will be blocked by the new person.
After we process all rows using the above logic, we follow a similar approach for each column to count the number of people visible below. We start from the bottommost position of each column and move upwards. Again, we use a stack to keep track of the visible heights.
Finally, we combine the counts of visible people from both horizontal and vertical directions. Each position (i, j)
in the output answer
is the sum of counts from corresponding row and column individual calculations.
By breaking this down into two 1-dimensional problems (first rows, then columns), we are able to solve the whole 2-dimensional problem efficiently.
Learn more about Stack and Monotonic Stack patterns.
Solution Approach
The solution uses the function f(nums: List[int]) -> List[int]
to process a 1-dimensional list of heights and returns a list of counts representing how many people can be seen from each position when looking in one direction (to the right for rows or upwards for columns). It utilizes a stack data structure, which is a LIFO (Last In, First Out) structure to keep track of potential candidates that people can see.
For each row in the heights
array, we call this function and treat the row as a standalone list:
- We iterate from right to left (the reverse direction) because we want to check how many people each person can see to their right.
- We use a stack (
stk
) to maintain the heights of the people in the current line of sight. - For each person, we compare their height with the height at the top of the stack. If the current person is taller:
- We pop all the people off the stack that are shorter than the current person and increment the visible count for the current person (since they can see over these shorter people).
- If there is someone left on the stack after this, we increment the count by one since the current person can see this next one.
- We also pop any people off the stack who are the same height as the current person, since they will be blocked.
- We push the current person's height onto the stack, signifying that subsequent shorter people will be able to see this person.
After processing all rows, we repeat a similar approach for each column. We extract each column from the heights
array and treat it as an independent list to pass to the f
function:
- We iterate from the bottom to the top since we want to determine how many people can be seen upwards.
- We apply the same logic with the stack to calculate the visible counts as we did for rows.
Finally, for each position (i, j)
, we sum up the values from the row and column processing – the resulting ans[i][j]
will contain the total number of people the person at (i, j)
can see to their right and below them.
The primary algorithms and patterns used here are iteration, stack data structure (for efficiently finding larger elements and keeping track), and reversing the direction of iteration (to maintain the correct order of sight based on problem constraints). With this approach, each person is processed in O(1)
time on average (since each element is pushed and popped from the stack once), leading to an overall time complexity of O(m*n)
where m
is the number of rows and n
is the number of columns.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's consider a small example to illustrate the solution approach. Imagine we have the following heights
array:
[ [3, 1], [4, 2] ]
Here we have 2 rows and 2 columns (m=2
, n=2
). Now let's walk through the algorithm:
For rows:
-
For the first row
[3, 1]
:- We start from the last person (rightmost), which has a height of
1
. - There's nobody to the right, so push
1
onto the stack. - Move to the next left person with height
3
. Since3
is taller than1
,3
can see over1
. - Pop
1
out of the stack, increment visibility count for3
by1
. - Push
3
onto the stack.
- We start from the last person (rightmost), which has a height of
-
After processing the first row, our visible count array is
[1, 0]
. This means the person at(0, 0)
can see1
person to their right, the person at(0, 1)
can see nobody to their right. -
For the second row
[4, 2]
, repeat similar steps:- Push
2
onto the stack. - Next,
4
is taller than2
, so4
can see over2
. - Pop
2
out, increment the visibility count for4
by1
. - Push
4
onto the stack.
- Push
-
After processing the second row, our visible count array is
[1, 0]
. Same as the first row, person at(1, 0)
can see1
person, person at(1, 1)
can see nobody to their right.
For columns:
-
For the first column
[3, 4]
(using the originalheights
array):- Starting from the bottom,
4
doesn't see anybody above it, so push4
onto the stack. - Moving up,
3
cannot see over4
because4
is taller.
- Starting from the bottom,
-
Visible count for the first column is
[0, 0]
. Nobody can see anyone above them because the bottom person is taller. -
For the second column
[1, 2]
:- Push
2
onto the stack. 1
is shorter than2
, so it doesn't see anyone above it.
- Push
-
Visible count for the second column is also
[0, 0]
.
Finally, we combine the counts from rows and columns:
- For
(0,0)
, the count is1 (row) + 0 (column) = 1
- For
(0,1)
, the count is0 (row) + 0 (column) = 0
- For
(1,0)
, the count is1 (row) + 0 (column) = 1
- For
(1,1)
, the count is0 (row) + 0 (column) = 0
Our final answer
array showcasing how many people each person at (i, j)
can see to their right and below them will be:
[ [1, 0], [1, 0] ]
In this example, the person at position (0, 0)
can see 1 person, and so can the person at (1, 0)
, while the others cannot see anyone based on the given problem constraints.
Solution Implementation
1class Solution:
2 def seePeople(self, heights: List[List[int]]) -> List[List[int]]:
3 # Define a function that calculates the number of people seen in a line
4 def count_people_seen(nums: List[int]) -> List[int]:
5 length = len(nums)
6 stack = []
7 visible_counts = [0] * length
8 # Iterate through each position from right to left
9 for i in reversed(range(length)):
10 # While there are people in the stack and the current person is taller
11 while stack and stack[-1] < nums[i]:
12 visible_counts[i] += 1
13 stack.pop()
14 # If there is still someone in the stack, increment count (can see at least one more person)
15 if stack:
16 visible_counts[i] += 1
17 # Pop the stack if the person at the top has the same height as the current one
18 while stack and stack[-1] == nums[i]:
19 stack.pop()
20 # Add current person to the stack
21 stack.append(nums[i])
22 return visible_counts
23
24 # Calculate people seen for each row using the helper function
25 row_visible = [count_people_seen(row) for row in heights]
26
27 # Get the dimensions of the heights matrix
28 rows_count, columns_count = len(heights), len(heights[0])
29
30 # For each column, calculate how many people can be seen vertically
31 for col in range(columns_count):
32 column_heights = [heights[row][col] for row in range(rows_count)]
33 column_visible = count_people_seen(column_heights)
34 # Add the numbers to the totals for each row in the final answer
35 for row in range(rows_count):
36 row_visible[row][col] += column_visible[row]
37
38 # Return the final matrix with counts of people visible from each cell
39 return row_visible
40
1class Solution {
2
3 // Method to calculate the number of people that can be seen in each row and column
4 public int[][] seePeople(int[][] heights) {
5 int rows = heights.length;
6 int cols = heights[0].length;
7
8 // The answer array to hold the number of people seen in both rows and columns
9 int[][] answer = new int[rows][cols];
10
11 // First, we calculate the number of people seen in each row
12 for (int i = 0; i < rows; ++i) {
13 answer[i] = calculateVisiblePeople(heights[i]);
14 }
15
16 // Then, we calculate the number of people seen in each column
17 for (int j = 0; j < cols; ++j) {
18 int[] columnHeights = new int[rows];
19 for (int i = 0; i < rows; ++i) {
20 columnHeights[i] = heights[i][j];
21 }
22 int[] columnVisible = calculateVisiblePeople(columnHeights);
23 for (int i = 0; i < rows; ++i) {
24 answer[i][j] += columnVisible[i];
25 }
26 }
27
28 return answer;
29 }
30
31 // Helper method to calculate the number of people visible in a line (row or column)
32 private int[] calculateVisiblePeople(int[] heights) {
33 int length = heights.length;
34 int[] visibleCount = new int[length];
35 Deque<Integer> stack = new ArrayDeque<>();
36
37 // We start from the end and move to the beginning to count visible people
38 for (int i = length - 1; i >= 0; --i) {
39 while (!stack.isEmpty() && stack.peek() < heights[i]) {
40 stack.pop();
41 ++visibleCount[i];
42 }
43 // Count the first person higher than the current one
44 if (!stack.isEmpty()) {
45 ++visibleCount[i];
46 }
47 // Remove all subsequent people of the same height
48 while (!stack.isEmpty() && stack.peek() == heights[i]) {
49 stack.pop();
50 }
51 stack.push(heights[i]);
52 }
53 return visibleCount;
54 }
55}
56
1#include <vector>
2#include <stack>
3
4using namespace std;
5
6class Solution {
7public:
8 // Main function that takes a 2D vector of heights and returns a 2D vector of visible people.
9 vector<vector<int>> seePeople(vector<vector<int>>& heights) {
10 int rows = heights.size(), cols = heights[0].size();
11
12 // Inner lambda function to find the number of visible people in a line (either a row or a column)
13 auto countVisible = [](vector<int>& line) {
14 int length = line.size();
15 vector<int> visibleCount(length);
16 stack<int> decreasingHeights;
17
18 // Start from the end of the line and work backwards
19 for (int i = length - 1; i >= 0; --i) {
20 // Pop heights that are shorter than the current height
21 while (!decreasingHeights.empty() && decreasingHeights.top() < line[i]) {
22 ++visibleCount[i];
23 decreasingHeights.pop();
24 }
25
26 // There is a taller or equal person blocking the view; increment once
27 if (!decreasingHeights.empty()) {
28 ++visibleCount[i];
29 }
30
31 // Pop all instances of the current height to maintain strictly decreasing order
32 while (!decreasingHeights.empty() && decreasingHeights.top() == line[i]) {
33 decreasingHeights.pop();
34 }
35
36 // Push the current height onto the stack
37 decreasingHeights.push(line[i]);
38 }
39 return visibleCount;
40 };
41
42 // This will store the final answer
43 vector<vector<int>> answer;
44
45 // Calculate visible people in each row
46 for (auto& row : heights) {
47 answer.push_back(countVisible(row));
48 }
49
50 // Calculate visible people in each column
51 for (int j = 0; j < cols; ++j) {
52 vector<int> column;
53 // Collect heights for the current column
54 for (int i = 0; i < rows; ++i) {
55 column.push_back(heights[i][j]);
56 }
57
58 // Use the countVisible function on the current column
59 vector<int> additionalVisible = countVisible(column);
60
61 // Add the results of the column to the answer
62 for (int i = 0; i < rows; ++i) {
63 answer[i][j] += additionalVisible[i];
64 }
65 }
66 return answer;
67 }
68};
69
1function seePeople(heights: number[][]): number[][] {
2 // Function to calculate the number of taller people visible
3 const calculateVisiblePeople = (nums: number[]): number[] => {
4 const length = nums.length;
5 const visibleCounts: number[] = new Array(length).fill(0);
6 const stack: number[] = [];
7 // Loop through the array from end to start
8 for (let i = length - 1; i >= 0; --i) {
9 // Remove elements from the stack that are shorter or equal
10 // since they can't be seen because the current person is taller or the same height
11 while (stack.length && stack.at(-1) <= nums[i]) {
12 stack.pop();
13 }
14 // If the stack isn't empty after the removals, increment the visible count
15 if (stack.length) {
16 ++visibleCounts[i];
17 }
18 // Push the current person onto the stack
19 stack.push(nums[i]);
20 }
21 return visibleCounts;
22 };
23
24 // Array to store the final result
25 const result: number[][] = [];
26 // Calculate visible people for each row
27 for (const row of heights) {
28 result.push(calculateVisiblePeople(row));
29 }
30
31 // Calculate visible people for each column
32 const width = heights[0].length;
33 for (let j = 0; j < width; ++j) {
34 // Extract the column from the heights matrix
35 const column: number[] = [];
36 for (const row of heights) {
37 column.push(row[j]);
38 }
39 // Calculate the additional visible people in this column
40 const additionalVisible = calculateVisiblePeople(column);
41 // Add the counts from the column to the relevant position in the result
42 for (let i = 0; i < result.length; ++i) {
43 result[i][j] += additionalVisible[i];
44 }
45 }
46 return result;
47}
48
Time and Space Complexity
The given code defines a method seePeople
that takes a 2D list of integers representing the heights and returns a 2D list representing how many people each person can see in their row and column. There are two main parts in the code: 1) the nested function f
processes a list to find how many people can be seen by each person in a single row or column, and 2) the main function applies this logic to each row and column in the given heights
matrix.
Time Complexity:
The nested function f(nums)
has the time complexity of O(n)
for a single row or column since it processes each element once, popping from the stack takes O(1)
time per operation, but each element can only be popped once. Hence, for processing one row or column of size n
, it would be O(n)
.
When the function f
is applied to each row:
- There are
m
rows, and the length of each row isn
. - The function is called
m
times, leading to a time complexity ofO(m * n)
for processing all rows
For the columns:
- The same function is being applied to each of
n
columns, and each column hasm
items. - This leads to a time complexity of
O(n * m)
once again for processing all columns.
Since we process both rows and columns separately, the total time complexity is O(m * n) + O(n * m)
which simplifies to O(2 * m * n)
and further to O(m * n)
since constant factors are dropped in Big O notation.
Space Complexity:
The auxiliary space is used for the stack within function f
and the list that holds answers.
- The stack
stk
in its worst case can grow toO(n)
for a row orO(m)
for a column, i.e., the size of the row or column it's processing. - The list
ans
is initialized with the same size as the input listnums
, so it'sO(n)
for a row andO(m)
for a column. - When processing rows, this is done in-place, so we only need space for the stack and one row's
ans
, which isO(n)
.
Since for the columns, a similar stack is used and the add
list is generated for each column, the space complexity would also be O(m)
at any instance since we process each column one at a time.
- Temporarily, for the
add
list generated for columns, it'sO(m)
as well.
So, the overall space complexity would be O(m + n)
. However, typically, for space complexity, we mention the higher term if m
and n
are not in the same range. Thus, if m
is approximately equal to n
, space complexity can be simplified to O(n)
.
Learn more about how to find time and space complexity quickly using problem constraints.
How would you design a stack which has a function min
that returns the minimum element in the stack, in addition to push
and pop
? All push
, pop
, min
should have running time O(1)
.
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
LeetCode 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 we
Want a Structured Path to Master System Design Too? Don’t Miss This!