Facebook Pixel

1139. Largest 1-Bordered Square

Problem Description

You are given a 2D grid containing only 0s and 1s. Your task is to find the largest square subgrid where all the cells on the border of the square contain 1s. 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 1s 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 1s 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 1s
  • The interior cells don't matter - they can be 0 or 1
  • 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 1s would return 9 (since 3 × 3 = 9).

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

Intuition

To check if a square has all 1s 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 1s extend downward from this position
  • How many consecutive 1s 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 consecutive 1s going down from (i, j) - we can check if down[i][j] >= k
  • The top side needs k consecutive 1s going right from (i, j) - we can check if right[i][j] >= k
  • The right side needs k consecutive 1s going down from (i, j+k-1) - we can check if down[i][j+k-1] >= k
  • The bottom side needs k consecutive 1s going right from (i+k-1, j) - we can check if right[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 1s on its border.

Step 1: Preprocess the grid with prefix sums

We create two 2D arrays:

  • down[i][j]: counts consecutive 1s going downward from position (i, j)
  • right[i][j]: counts consecutive 1s 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] equals 1 + down[i+1][j] (or 1 if at the bottom edge)
  • right[i][j] equals 1 + right[i][j+1] (or 1 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 (needs k consecutive 1s going down)
  • Top side: right[i][j] >= k (needs k consecutive 1s 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 Evaluator

Example 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

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:

  1. Preprocessing phase: Computing the down and right arrays requires iterating through all cells in the grid once, which takes O(m × n) time.

  2. Search phase: The algorithm searches for the largest square by:

    • Iterating through all possible square sizes k from min(m, n) down to 1, which gives us O(min(m, n)) iterations
    • For each square size k, checking all possible top-left positions (i, j) where a square of size k could fit, which requires O((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))

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 cell
  • right[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 least square_size consecutive 1s starting from the left corner
  • consecutive_ones_down[top_row][right_col] >= square_size ensures the right border has at least square_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 it
  • consecutive_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.

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

What are the two properties the problem needs to have for dynamic programming to be applicable? (Select 2)


Recommended Readings

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

Load More