Tiling a Rectangle with the Fewest Squares asks you to cover a rectangle using whole squares, minimizing how many you use. It is a classic backtracking problem: there is no clean formula, so we search every reasonable arrangement while aggressively pruning the hopeless ones.
Problem. Given a rectangle of width W and height H, return the minimum number of integer-sided
squares that tile it completely (no gaps, no overlaps).
Example: W = 3, H = 2 → answer 3 (one 2 × 2 square plus two 1 × 1 squares).
The slow way first
You might hope for a formula, but minimal square tilings are surprisingly irregular — for some sizes the optimal layout looks nothing like a neat grid. The honest approach is to try placements and search. The danger is that the naive search explodes: at every empty cell you could place many square sizes, and the tree of choices grows enormous. We need structure to tame it.
The key question: where should I place the next square, and how do I avoid exploring layouts that already lost?
The idea: lowest-leftmost gap, then prune by best
Two ideas keep the search small. First, always fill the lowest, leftmost empty cell. Track a heights array — how high each column is filled. The next square must sit at the shortest column. This removes the freedom to place squares in random order, so each distinct tiling is reached once.
Second, prune by the best answer so far. Keep best, the fewest squares seen in any complete tiling. If a partial layout already used count >= best squares and is not finished, it can never win — abandon it immediately.
At each gap we try squares from the largest that fits down to 1 × 1. Trying big first tends to find a good best quickly, which makes pruning sharper for everything after.
Walk through it
Step through the animation on a 3 × 2 board. The shortest column starts at the far left, where a 2 × 2 fits. After placing it, the only gaps are the two cells of the rightmost column, each taking a 1 × 1. That completes the board with count = 3, so best = 3. Backtracking then explores other openings, but any path that reaches count = 3 without finishing is pruned. The answer is 3.
Pseudocode
best = W * H # worst case: every cell is its own 1x1
function dfs(heights, count):
if every column is full:
best = min(best, count); return
if count >= best: # this branch cannot beat best
return
c = index of the shortest column
max_size = min(remaining height at c, remaining width from c)
for size from max_size down to 1:
place a size x size square at column c
dfs(heights, count + 1)
remove that square (backtrack)
dfs(all zeros, 0)
return bestThe Python solution
def tiling(W, H):
best = W * H # worst case: all 1x1
def dfs(heights, count):
nonlocal best
if all(h == H for h in heights):
best = min(best, count); return
if count >= best:
return # prune: cannot beat best
c = heights.index(min(heights))
for size in range(min(H - heights[c], W - c), 0, -1):
place(heights, c, size); dfs(heights, count + 1); unplace(heights, c, size)
dfs([0] * W, 0)
return bestbeststarts atW * H, the all-1 × 1 worst case, so any complete tiling improves it.- The first
ifis the goal test: when every column reaches heightH, the board is full — recordbestand return. if count >= bestis the prune: an unfinished layout already at the budget cannot win, so we stop.heights.index(min(heights))picks the lowest, leftmost column — our fixed placement rule.min(H - heights[c], W - c)is the largest square that fits without overflowing height or width; we try sizes largest first, then backtrack withunplace.
Complexity
| Case | Time | Notes |
|---|---|---|
| Naive search (any order) | exponential (moderate) | squares placeable anywhere |
| Lowest-gap + best pruning | exponential but tiny (moderate) | for the bounded W, H <= 13 inputs |
O(W) heights + O(W*H) recursion depth (moderate)The worst case is still exponential, but the lowest-leftmost rule plus pruning by best shrinks the real search to something instant for the constrained sizes these problems use.
When this pattern shows up
When a layout or assignment problem has no formula and asks for a minimum or maximum, reach for backtracking with a fixed placement order plus a best-so-far prune. Forcing one canonical order (here: always the shortest column) avoids exploring the same arrangement many ways, and the prune cuts whole subtrees the instant they cannot beat your current answer.
Do not let squares be placed in arbitrary cells. Without the lowest-leftmost rule you revisit the same tiling through different orderings and the search blows up. The single fixed gap choice is what makes this tractable.
Practice
On the 3 x 2 board, after placing the first 2 x 2 square at column 0, what is the heights array and which column is the next gap?
1. Why do we always place the next square at the lowest, leftmost empty cell?
2. What does the prune 'if count >= best: return' accomplish?
3. Why try square sizes from largest down to smallest at each gap?
4. For the 3 x 2 board, what is the minimum number of squares?