Tiling a Rectangle with the Fewest Squares
HardPrompt
Given a rectangle of size n x m, return the minimum number of integer-sided squares that tile the rectangle.
Example 1:

3Example 2:

Input: n = 5, m = 8
Output: 5Example 3:

Input: n = 11, m = 13
Output: 6
Constraints:
1 <= n, m <= 13
Approaches
2 approaches with complexity analysis and trade-offs.
This approach uses a straightforward backtracking algorithm to explore all possible ways of tiling the rectangle. It uses a 2D grid to keep track of covered cells and recursively tries to place squares in the first available empty spot. While simple to understand, its brute-force nature makes it highly inefficient.
Algorithm
- The state of the tiling is represented by a 2D boolean grid
covered[n][m], wheretrueindicates a cell is covered. - A global variable
ansstores the minimum number of squares found, initialized to a large value (e.g.,n * m). - A recursive function,
dfs(covered, count), explores tiling possibilities. - Pruning: If the current count of squares
countis already greater than or equal toans, the function returns immediately. - Base Case: The function scans the grid to find the first uncovered cell
(r, c). If no such cell is found, the rectangle is fully tiled.ansis updated withmin(ans, count). - Recursive Step:
- For the uncovered cell
(r, c), the algorithm tries to place squares of every possible sizes(from largest to smallest) that can fit. - To place a square, it must fit within the rectangle's boundaries and not overlap with any already covered cells.
- For each valid placement:
- Mark the corresponding cells in the
coveredgrid astrue. - Make a recursive call:
dfs(covered, count + 1). - Backtrack: Revert the changes to the
coveredgrid by marking the cells back tofalse.
- Mark the corresponding cells in the
- For the uncovered cell
Walkthrough
The core idea is to perform a depth-first search (DFS) on the state of the rectangle. The state is defined by which cells are covered. We start with a completely empty rectangle and recursively add one square at a time.
We begin by finding the top-most, left-most empty cell. Then, we try to place the largest possible square that covers this cell. After placing the square, we make a recursive call to solve for the rest of the rectangle. Once the recursive call returns, we backtrack by removing the square and trying the next smaller size. This process continues until all possibilities are exhausted.
To avoid exploring fruitless paths, we use pruning. A global variable ans holds the minimum number of squares found so far. If the current number of squares used in a path (count) meets or exceeds ans, we abandon that path.
class Solution { int ans; public int tilingRectangle(int n, int m) { if (n == m) return 1; ans = n * m; boolean[][] covered = new boolean[n][m]; dfs(n, m, 0, 0, covered, 0); return ans; } private void dfs(int n, int m, int r, int c, boolean[][] covered, int count) { if (count >= ans) { return; } // Find the first uncovered cell, starting from (r, c) while (r < n && covered[r][c]) { c++; if (c == m) { r++; c = 0; } } // If all cells are covered, we found a solution if (r == n) { ans = Math.min(ans, count); return; } // Try to place squares of different sizes, largest first for better pruning for (int size = Math.min(n - r, m - c); size >= 1; size--) { if (canPlace(covered, r, c, size)) { place(covered, r, c, size, true); dfs(n, m, r, c + size, covered, count + 1); place(covered, r, c, size, false); // backtrack } } } private boolean canPlace(boolean[][] covered, int r, int c, int size) { for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { if (covered[r + i][c + j]) { return false; } } } return true; } private void place(boolean[][] covered, int r, int c, int size, boolean val) { for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { covered[r + i][c + j] = val; } } }}Complexity
Time
Exponential. The search space is vast, related to the number of ways to tile a grid. Without effective pruning, the complexity is prohibitive. This approach is too slow for the given constraints.
Space
O(n * m) to store the `covered` grid. The recursion stack depth also adds to the space, but it's dominated by the grid.
Trade-offs
Pros
Guarantees finding the optimal solution.
Relatively easy to conceptualize and implement.
Cons
Extremely inefficient due to the large state representation (
n*mgrid).The process of finding the next available cell and checking for valid placement is slow.
Very likely to cause a 'Time Limit Exceeded' error for most inputs within the given constraints.
Solutions
Solution
class Solution {private int n;private int m;private int[] filled;private int ans;public int tilingRectangle(int n, int m) { this.n = n; this.m = m; ans = n * m; filled = new int[n]; dfs(0, 0, 0); return ans; }private void dfs(int i, int j, int t) { if (j == m) { ++i; j = 0; } if (i == n) { ans = t; return; } if ((filled[i] >> j & 1) == 1) { dfs(i, j + 1, t); } else if (t + 1 < ans) { int r = 0, c = 0; for (int k = i; k < n; ++k) { if ((filled[k] >> j & 1) == 1) { break; } ++r; } for (int k = j; k < m; ++k) { if ((filled[i] >> k & 1) == 1) { break; } ++c; } int mx = Math.min(r, c); for (int w = 1; w <= mx; ++w) { for (int k = 0; k < w; ++k) { filled[i + w - 1] |= 1 << (j + k); filled[i + k] |= 1 << (j + w - 1); } dfs(i, j + w, t + 1); } for (int x = i; x < i + mx; ++x) { for (int y = j; y < j + mx; ++y) { filled[x] ^= 1 << y; } } } }}Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.