1139. Largest 1-Bordered Square
Problem Description
You are given a 2D grid containing only 0
s and 1
s. Your task is to find the largest square subgrid where all the cells on the border of the square contain 1
s. The interior cells can be either 0
or 1
- only the border matters.
For example, if you have a 3×3 square, you need all 8 border cells (the outer frame) to be 1
s to consider it valid. The center cell can be anything.
The problem asks you to return the number of elements in the largest such square (which is the area, or side length squared). If no valid square with all 1
s on its border exists in the grid, return 0
.
Key points to understand:
- You're looking for a square subgrid (not rectangle)
- Only the border cells of the square need to be
1
s - The interior cells don't matter - they can be
0
or1
- You want the largest possible such square
- Return the total number of cells in that square (side × side)
For instance, a valid 3×3 square bordered by 1
s would return 9
(since 3 × 3 = 9).
Intuition
To check if a square has all 1
s on its border, we need to verify four sides: top, bottom, left, and right. A naive approach would be to check each cell on the border for every possible square, but this would be inefficient.
The key insight is that we can precompute useful information to speed up our border checks. For any position (i, j)
in the grid, if we know:
- How many consecutive
1
s extend downward from this position - How many consecutive
1
s extend to the right from this position
Then we can quickly verify if a square's border is valid.
Why does this help? Consider a square with top-left corner at (i, j)
and side length k
:
- The left side of the square needs
k
consecutive1
s going down from(i, j)
- we can check ifdown[i][j] >= k
- The top side needs
k
consecutive1
s going right from(i, j)
- we can check ifright[i][j] >= k
- The right side needs
k
consecutive1
s going down from(i, j+k-1)
- we can check ifdown[i][j+k-1] >= k
- The bottom side needs
k
consecutive1
s going right from(i+k-1, j)
- we can check ifright[i+k-1][j] >= k
By preprocessing these down
and right
arrays using dynamic programming (working backwards from bottom-right), we can check any square's validity in O(1)
time.
Since we want the largest square, we can enumerate side lengths from largest to smallest. As soon as we find a valid square of side length k
, we can immediately return k * k
since any larger square would have been found earlier in our search.
Learn more about Dynamic Programming patterns.
Solution Approach
The solution uses prefix sum preprocessing combined with enumeration to efficiently find the largest square with all 1
s on its border.
Step 1: Preprocess the grid with prefix sums
We create two 2D arrays:
down[i][j]
: counts consecutive1
s going downward from position(i, j)
right[i][j]
: counts consecutive1
s going to the right from position(i, j)
We fill these arrays by iterating from bottom-right to top-left:
for i in range(m - 1, -1, -1):
for j in range(n - 1, -1, -1):
if grid[i][j]:
down[i][j] = down[i + 1][j] + 1 if i + 1 < m else 1
right[i][j] = right[i][j + 1] + 1 if j + 1 < n else 1
For each cell with value 1
:
down[i][j]
equals1 + down[i+1][j]
(or1
if at the bottom edge)right[i][j]
equals1 + right[i][j+1]
(or1
if at the right edge)
Step 2: Enumerate possible square sizes
We try square sizes from largest to smallest (min(m, n)
down to 1
):
for k in range(min(m, n), 0, -1):
Step 3: Check all possible positions for each square size
For each square size k
, we check all valid top-left corner positions (i, j)
:
for i in range(m - k + 1):
for j in range(n - k + 1):
Step 4: Validate the square's border
A square with top-left at (i, j)
and side length k
has valid borders if:
- Left side:
down[i][j] >= k
(needsk
consecutive1
s going down) - Top side:
right[i][j] >= k
(needsk
consecutive1
s going right) - Right side:
down[i][j + k - 1] >= k
(from top-right corner going down) - Bottom side:
right[i + k - 1][j] >= k
(from bottom-left corner going right)
If all four conditions are met, we immediately return k * k
since we're checking from largest to smallest.
Time Complexity: O(m * n * min(m, n))
where preprocessing takes O(m * n)
and checking all squares takes O(m * n * min(m, n))
.
Space Complexity: O(m * n)
for the two auxiliary arrays.
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 finding the largest square with all 1s on its border using this grid:
grid = [[1, 1, 1], [1, 0, 1], [1, 1, 1]]
Step 1: Build the preprocessing arrays
We create down
and right
arrays by working backwards from bottom-right to top-left.
Starting with down
(counts consecutive 1s going downward):
down[2][2] = 1
(bottom-right, only itself)down[2][1] = 1
(grid[2][1] = 1, but it's the bottom row)down[2][0] = 1
(bottom-left, only itself)down[1][2] = 1 + down[2][2] = 2
(grid[1][2] = 1, adds to below)down[1][1] = 0
(grid[1][1] = 0, breaks the chain)down[1][0] = 1 + down[2][0] = 2
(grid[1][0] = 1, adds to below)down[0][2] = 1 + down[1][2] = 3
(all three cells going down are 1s)down[0][1] = 1
(grid[0][1] = 1, but grid[1][1] = 0 below)down[0][0] = 1 + down[1][0] = 3
(all three cells going down are 1s)
down = [[3, 1, 3], [2, 0, 2], [1, 1, 1]]
Similarly for right
(counts consecutive 1s going rightward):
right = [[3, 2, 1], [1, 0, 1], [3, 2, 1]]
Step 2: Check squares from largest to smallest
Starting with size k=3 (the maximum possible):
- Check position (0,0) for a 3×3 square:
- Left side:
down[0][0] = 3 >= 3
✓ - Top side:
right[0][0] = 3 >= 3
✓ - Right side:
down[0][2] = 3 >= 3
✓ - Bottom side:
right[2][0] = 3 >= 3
✓
- Left side:
All four borders are valid! We found a 3×3 square with all 1s on its border.
Return: 3 × 3 = 9
Note that the center cell grid[1][1] = 0 doesn't matter - only the 8 border cells need to be 1s, which they are:
Border cells: [1, 1, 1] [1, _, 1] [1, 1, 1]
If this 3×3 square hadn't worked, we would continue checking smaller sizes (k=2, then k=1) until finding a valid square or returning 0 if none exist.
Solution Implementation
1class Solution:
2 def largest1BorderedSquare(self, grid: List[List[int]]) -> int:
3 # Get dimensions of the grid
4 rows, cols = len(grid), len(grid[0])
5
6 # Create DP tables to store consecutive 1s count
7 # consecutive_ones_down[i][j] = number of consecutive 1s going down from (i,j)
8 # consecutive_ones_right[i][j] = number of consecutive 1s going right from (i,j)
9 consecutive_ones_down = [[0] * cols for _ in range(rows)]
10 consecutive_ones_right = [[0] * cols for _ in range(rows)]
11
12 # Build the DP tables by iterating from bottom-right to top-left
13 for row in range(rows - 1, -1, -1):
14 for col in range(cols - 1, -1, -1):
15 if grid[row][col] == 1:
16 # Count consecutive 1s going down from current position
17 if row + 1 < rows:
18 consecutive_ones_down[row][col] = consecutive_ones_down[row + 1][col] + 1
19 else:
20 consecutive_ones_down[row][col] = 1
21
22 # Count consecutive 1s going right from current position
23 if col + 1 < cols:
24 consecutive_ones_right[row][col] = consecutive_ones_right[row][col + 1] + 1
25 else:
26 consecutive_ones_right[row][col] = 1
27
28 # Try to find the largest square, starting from maximum possible size
29 max_possible_size = min(rows, cols)
30
31 for square_size in range(max_possible_size, 0, -1):
32 # Check all possible top-left corners for current square size
33 for top_row in range(rows - square_size + 1):
34 for left_col in range(cols - square_size + 1):
35 # Calculate positions of the four corners
36 bottom_row = top_row + square_size - 1
37 right_col = left_col + square_size - 1
38
39 # Check if all four borders have enough consecutive 1s
40 has_top_border = consecutive_ones_right[top_row][left_col] >= square_size
41 has_left_border = consecutive_ones_down[top_row][left_col] >= square_size
42 has_bottom_border = consecutive_ones_right[bottom_row][left_col] >= square_size
43 has_right_border = consecutive_ones_down[top_row][right_col] >= square_size
44
45 if has_top_border and has_left_border and has_bottom_border and has_right_border:
46 # Found a valid square with all borders made of 1s
47 return square_size * square_size
48
49 # No valid square found
50 return 0
51
1class Solution {
2 public int largest1BorderedSquare(int[][] grid) {
3 int rows = grid.length;
4 int cols = grid[0].length;
5
6 // dp arrays to store consecutive 1s count
7 // downCount[i][j] = number of consecutive 1s going down from position (i,j)
8 // rightCount[i][j] = number of consecutive 1s going right from position (i,j)
9 int[][] downCount = new int[rows][cols];
10 int[][] rightCount = new int[rows][cols];
11
12 // Build the dp arrays from bottom-right to top-left
13 for (int i = rows - 1; i >= 0; i--) {
14 for (int j = cols - 1; j >= 0; j--) {
15 if (grid[i][j] == 1) {
16 // Calculate consecutive 1s going down
17 downCount[i][j] = (i + 1 < rows) ? downCount[i + 1][j] + 1 : 1;
18
19 // Calculate consecutive 1s going right
20 rightCount[i][j] = (j + 1 < cols) ? rightCount[i][j + 1] + 1 : 1;
21 }
22 }
23 }
24
25 // Try all possible square sizes from largest to smallest
26 int maxPossibleSize = Math.min(rows, cols);
27 for (int squareSize = maxPossibleSize; squareSize > 0; squareSize--) {
28 // Check all possible top-left corners for current square size
29 for (int topRow = 0; topRow <= rows - squareSize; topRow++) {
30 for (int leftCol = 0; leftCol <= cols - squareSize; leftCol++) {
31 // Verify if a valid bordered square exists at this position
32 // Check: 1. Top-left corner has enough 1s going down (left border)
33 // 2. Top-left corner has enough 1s going right (top border)
34 // 3. Bottom-left corner has enough 1s going right (bottom border)
35 // 4. Top-right corner has enough 1s going down (right border)
36 boolean hasValidTopBorder = rightCount[topRow][leftCol] >= squareSize;
37 boolean hasValidLeftBorder = downCount[topRow][leftCol] >= squareSize;
38 boolean hasValidBottomBorder = rightCount[topRow + squareSize - 1][leftCol] >= squareSize;
39 boolean hasValidRightBorder = downCount[topRow][leftCol + squareSize - 1] >= squareSize;
40
41 if (hasValidTopBorder && hasValidLeftBorder &&
42 hasValidBottomBorder && hasValidRightBorder) {
43 // Found the largest valid square, return its area
44 return squareSize * squareSize;
45 }
46 }
47 }
48 }
49
50 // No valid bordered square found
51 return 0;
52 }
53}
54
1class Solution {
2public:
3 int largest1BorderedSquare(vector<vector<int>>& grid) {
4 int rows = grid.size();
5 int cols = grid[0].size();
6
7 // dp arrays to store consecutive 1s count
8 // consecutiveDown[i][j]: number of consecutive 1s going down from (i,j)
9 // consecutiveRight[i][j]: number of consecutive 1s going right from (i,j)
10 int consecutiveDown[rows][cols];
11 int consecutiveRight[rows][cols];
12
13 // Initialize arrays with 0
14 memset(consecutiveDown, 0, sizeof(consecutiveDown));
15 memset(consecutiveRight, 0, sizeof(consecutiveRight));
16
17 // Build the consecutive count arrays from bottom-right to top-left
18 for (int i = rows - 1; i >= 0; --i) {
19 for (int j = cols - 1; j >= 0; --j) {
20 if (grid[i][j] == 1) {
21 // Count consecutive 1s going down from current position
22 consecutiveDown[i][j] = (i + 1 < rows) ? consecutiveDown[i + 1][j] + 1 : 1;
23
24 // Count consecutive 1s going right from current position
25 consecutiveRight[i][j] = (j + 1 < cols) ? consecutiveRight[i][j + 1] + 1 : 1;
26 }
27 }
28 }
29
30 // Try all possible square sizes from largest to smallest
31 int maxPossibleSize = min(rows, cols);
32 for (int squareSize = maxPossibleSize; squareSize > 0; --squareSize) {
33 // Try all possible top-left corners for current square size
34 for (int topRow = 0; topRow <= rows - squareSize; ++topRow) {
35 for (int leftCol = 0; leftCol <= cols - squareSize; ++leftCol) {
36 // Check if all four borders have enough consecutive 1s
37 // Top-left corner needs at least squareSize 1s going down and right
38 bool topLeftValid = consecutiveDown[topRow][leftCol] >= squareSize &&
39 consecutiveRight[topRow][leftCol] >= squareSize;
40
41 // Bottom-left corner needs at least squareSize 1s going right
42 bool bottomLeftValid = consecutiveRight[topRow + squareSize - 1][leftCol] >= squareSize;
43
44 // Top-right corner needs at least squareSize 1s going down
45 bool topRightValid = consecutiveDown[topRow][leftCol + squareSize - 1] >= squareSize;
46
47 if (topLeftValid && bottomLeftValid && topRightValid) {
48 // Found valid square with all borders made of 1s
49 return squareSize * squareSize;
50 }
51 }
52 }
53 }
54
55 // No valid square found
56 return 0;
57 }
58};
59
1function largest1BorderedSquare(grid: number[][]): number {
2 const rows: number = grid.length;
3 const cols: number = grid[0].length;
4
5 // DP arrays to store consecutive 1s count
6 // consecutiveDown[i][j]: number of consecutive 1s going down from (i,j)
7 // consecutiveRight[i][j]: number of consecutive 1s going right from (i,j)
8 const consecutiveDown: number[][] = Array(rows).fill(0).map(() => Array(cols).fill(0));
9 const consecutiveRight: number[][] = Array(rows).fill(0).map(() => Array(cols).fill(0));
10
11 // Build the consecutive count arrays from bottom-right to top-left
12 // This allows us to count consecutive 1s in each direction efficiently
13 for (let i = rows - 1; i >= 0; i--) {
14 for (let j = cols - 1; j >= 0; j--) {
15 if (grid[i][j] === 1) {
16 // Count consecutive 1s going down from current position
17 // If there's a cell below, add 1 to its down count, otherwise this is the start (count = 1)
18 consecutiveDown[i][j] = (i + 1 < rows) ? consecutiveDown[i + 1][j] + 1 : 1;
19
20 // Count consecutive 1s going right from current position
21 // If there's a cell to the right, add 1 to its right count, otherwise this is the start (count = 1)
22 consecutiveRight[i][j] = (j + 1 < cols) ? consecutiveRight[i][j + 1] + 1 : 1;
23 }
24 }
25 }
26
27 // Try all possible square sizes from largest to smallest
28 // This ensures we find the maximum area as soon as possible
29 const maxPossibleSize: number = Math.min(rows, cols);
30
31 for (let squareSize = maxPossibleSize; squareSize > 0; squareSize--) {
32 // Try all possible top-left corners for current square size
33 for (let topRow = 0; topRow <= rows - squareSize; topRow++) {
34 for (let leftCol = 0; leftCol <= cols - squareSize; leftCol++) {
35 // Check if all four borders have enough consecutive 1s
36
37 // Top-left corner needs at least squareSize 1s going down and right
38 // This forms the top border (going right) and left border (going down)
39 const topLeftValid: boolean = consecutiveDown[topRow][leftCol] >= squareSize &&
40 consecutiveRight[topRow][leftCol] >= squareSize;
41
42 // Bottom-left corner needs at least squareSize 1s going right
43 // This forms the bottom border
44 const bottomLeftValid: boolean = consecutiveRight[topRow + squareSize - 1][leftCol] >= squareSize;
45
46 // Top-right corner needs at least squareSize 1s going down
47 // This forms the right border
48 const topRightValid: boolean = consecutiveDown[topRow][leftCol + squareSize - 1] >= squareSize;
49
50 if (topLeftValid && bottomLeftValid && topRightValid) {
51 // Found valid square with all borders made of 1s
52 // Return the area of the square
53 return squareSize * squareSize;
54 }
55 }
56 }
57 }
58
59 // No valid square found
60 return 0;
61}
62
Time and Space Complexity
Time Complexity: O(m × n × min(m, n))
The algorithm consists of two main parts:
-
Preprocessing phase: Computing the
down
andright
arrays requires iterating through all cells in the grid once, which takesO(m × n)
time. -
Search phase: The algorithm searches for the largest square by:
- Iterating through all possible square sizes
k
frommin(m, n)
down to 1, which gives usO(min(m, n))
iterations - For each square size
k
, checking all possible top-left positions(i, j)
where a square of sizek
could fit, which requiresO((m - k + 1) × (n - k + 1))
iterations - Each validity check for a square takes
O(1)
time
In the worst case, when no valid square is found until
k = 1
, the total iterations would be:Σ(k=1 to min(m,n)) (m - k + 1) × (n - k + 1) ≈ O(m × n × min(m, n))
- Iterating through all possible square sizes
Therefore, the overall time complexity is O(m × n × min(m, n))
.
Space Complexity: O(m × n)
The algorithm uses two auxiliary 2D arrays:
down[m][n]
: stores the count of consecutive 1s going downward from each cellright[m][n]
: stores the count of consecutive 1s going rightward from each cell
Both arrays have dimensions m × n
, resulting in a space complexity of O(m × n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Confusing Border Validation with Full Square Validation
A common mistake is thinking that checking the four corners' consecutive counts validates the entire border. However, the consecutive count arrays tell us about potential borders, not guaranteed complete borders.
The Pitfall:
When we check consecutive_ones_right[top_row][left_col] >= square_size
, we're verifying that there are enough consecutive 1s going right from the top-left corner. But this doesn't guarantee that ALL cells on the top border are part of our square - some of those consecutive 1s might extend beyond our square's boundary.
Why This Works Anyway: The algorithm is correct because:
- We check all four corners' consecutive counts
consecutive_ones_right[top_row][left_col] >= square_size
ensures the top border has at leastsquare_size
consecutive 1s starting from the left cornerconsecutive_ones_down[top_row][right_col] >= square_size
ensures the right border has at leastsquare_size
consecutive 1s starting from the top-right corner- The combination of all four checks guarantees the entire border is made of 1s
2. Off-by-One Errors in Index Calculations
The Pitfall: It's easy to make mistakes when calculating the bottom-right corner indices:
# Incorrect: bottom_row = top_row + square_size # This would be one row too far right_col = left_col + square_size # This would be one column too far # Correct: bottom_row = top_row + square_size - 1 right_col = left_col + square_size - 1
Solution:
Remember that if the top-left corner is at (i, j)
and the square has side length k
, then:
- Top-right corner:
(i, j + k - 1)
- Bottom-left corner:
(i + k - 1, j)
- Bottom-right corner:
(i + k - 1, j + k - 1)
3. Incorrect Preprocessing Direction
The Pitfall: Building the prefix arrays in the wrong direction:
# Incorrect - going from top-left to bottom-right:
for row in range(rows):
for col in range(cols):
if grid[row][col] == 1:
consecutive_ones_down[row][col] = consecutive_ones_down[row - 1][col] + 1 # Wrong!
Solution: The arrays must be built from bottom-right to top-left because:
consecutive_ones_down[i][j]
needs to know about cells below itconsecutive_ones_right[i][j]
needs to know about cells to its right- We can only compute these if we've already processed those cells
4. Not Handling Edge Cases in Preprocessing
The Pitfall: Forgetting to handle boundary conditions when building prefix arrays:
# Incorrect - might cause index out of bounds: consecutive_ones_down[row][col] = consecutive_ones_down[row + 1][col] + 1 # Correct - with boundary check: if row + 1 < rows: consecutive_ones_down[row][col] = consecutive_ones_down[row + 1][col] + 1 else: consecutive_ones_down[row][col] = 1
Solution: Always check if the next cell exists before accessing it. For cells on the edge, initialize the count to 1 if the cell itself contains a 1.
What are the two properties the problem needs to have for dynamic programming to be applicable? (Select 2)
Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
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
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!