You are given two positive integers, n
and m
, representing the dimensions of a rectangle (n
rows and m
columns). Your task is to tile the entire rectangle with the fewest number of integer-sided square tiles. Each tile must be a square (its sides must be of equal length), and you may use as many as you like, but you must use as few as possible. You may not overlap tiles, and you must cover the rectangle perfectly (no gaps or overlaps). Tiles cannot extend beyond the borders of the rectangle.
The output should be the minimum number of square tiles needed to cover the entire rectangle.
min(n, m)
.1 <= n, m <= 13
At first glance, one might consider simply filling the rectangle with the smallest possible tiles (1x1 squares), but this would obviously use the maximum number of tiles. Alternatively, you might try to use the largest possible square at each step, but this greedy approach doesn't always yield the optimal solution.
The key insight is that, due to the small constraints, we can afford to try all possible ways to place squares recursively but must optimize to avoid redundant computations. This leads us toward backtracking with memoization (also known as DFS with pruning and DP).
The challenge is to represent the current "state" of the rectangle efficiently, to avoid recomputing subproblems. We also need to decide, at each step, where to place the next square and what size it should be, always trying to minimize the total tile count.
We solve this problem using a recursive backtracking approach, enhanced with memoization to avoid redundant work. Here's the step-by-step approach:
(i, j)
).(i, j)
that does not overlap any already-covered cells or extend beyond the rectangle.This approach works well because the rectangle is small (maximum 13x13), so the number of states is manageable.
Let's consider n = 2, m = 3 (a 2x3 rectangle).
Brute-force approach:
The reason for the exponential complexity is that each cell can be covered in many different ways, and we have to consider all combinations.
The problem of tiling a rectangle with the fewest squares is a classic example of recursive search with memoization. The main insight is to represent the problem's state efficiently and prune unnecessary branches by always trying the largest possible square first. Despite the exponential number of possible tilings, the small constraints and clever use of memoization make the problem tractable. This solution demonstrates the power of combining recursion, backtracking, and dynamic programming to solve complex combinatorial problems efficiently.
from functools import lru_cache
class Solution:
def tilingRectangle(self, n: int, m: int) -> int:
if n == m:
return 1
@lru_cache(None)
def dfs(heights):
if all(h == m for h in heights):
return 0
min_height = min(heights)
idx = heights.index(min_height)
max_size = 1
while (idx + max_size <= n and
all(heights[i] == min_height for i in range(idx, idx + max_size)) and
min_height + max_size <= m):
max_size += 1
res = float('inf')
for size in range(max_size - 1, 0, -1):
new_heights = list(heights)
for i in range(idx, idx + size):
new_heights[i] += size
res = min(res, 1 + dfs(tuple(new_heights)))
return res
return dfs(tuple([0] * n))
#include <vector>
#include <algorithm>
#include <climits>
#include <unordered_map>
using namespace std;
class Solution {
public:
int tilingRectangle(int n, int m) {
if (n == m) return 1;
unordered_map<string, int> memo;
return dfs(vector<int>(n, 0), n, m, memo);
}
int dfs(vector<int>& heights, int n, int m, unordered_map<string, int>& memo) {
string key = vecToString(heights);
if (memo.count(key)) return memo[key];
bool done = true;
for (int h : heights) if (h != m) done = false;
if (done) return 0;
int min_h = INT_MAX, idx = -1;
for (int i = 0; i < n; ++i) {
if (heights[i] < min_h) {
min_h = heights[i];
idx = i;
}
}
int max_size = 1;
while (idx + max_size <= n) {
bool can = true;
for (int i = idx; i < idx + max_size; ++i)
if (heights[i] != min_h) can = false;
if (!can || min_h + max_size > m) break;
++max_size;
}
int res = INT_MAX;
for (int size = max_size - 1; size > 0; --size) {
for (int i = idx; i < idx + size; ++i)
heights[i] += size;
res = min(res, 1 + dfs(heights, n, m, memo));
for (int i = idx; i < idx + size; ++i)
heights[i] -= size;
}
memo[key] = res;
return res;
}
string vecToString(const vector<int>& v) {
string res;
for (int x : v) res += to_string(x) + ",";
return res;
}
};
import java.util.*;
class Solution {
public int tilingRectangle(int n, int m) {
if (n == m) return 1;
Map<String, Integer> memo = new HashMap<>();
return dfs(new int[n], n, m, memo);
}
private int dfs(int[] heights, int n, int m, Map<String, Integer> memo) {
String key = Arrays.toString(heights);
if (memo.containsKey(key)) return memo.get(key);
boolean done = true;
for (int h : heights) if (h != m) done = false;
if (done) return 0;
int minH = Integer.MAX_VALUE, idx = -1;
for (int i = 0; i < n; ++i) {
if (heights[i] < minH) {
minH = heights[i];
idx = i;
}
}
int maxSize = 1;
while (idx + maxSize <= n) {
boolean can = true;
for (int i = idx; i < idx + maxSize; ++i)
if (heights[i] != minH) can = false;
if (!can || minH + maxSize > m) break;
++maxSize;
}
int res = Integer.MAX_VALUE;
for (int size = maxSize - 1; size > 0; --size) {
for (int i = idx; i < idx + size; ++i)
heights[i] += size;
res = Math.min(res, 1 + dfs(heights, n, m, memo));
for (int i = idx; i < idx + size; ++i)
heights[i] -= size;
}
memo.put(key, res);
return res;
}
}
var tilingRectangle = function(n, m) {
if (n === m) return 1;
const memo = new Map();
function dfs(heights) {
const key = heights.join(',');
if (memo.has(key)) return memo.get(key);
if (heights.every(h => h === m)) return 0;
let minH = Math.min(...heights);
let idx = heights.indexOf(minH);
let maxSize = 1;
while (
idx + maxSize <= n &&
heights.slice(idx, idx + maxSize).every(h => h === minH) &&
minH + maxSize <= m
) {
maxSize++;
}
let res = Infinity;
for (let size = maxSize - 1; size > 0; size--) {
for (let i = idx; i < idx + size; i++) heights[i] += size;
res = Math.min(res, 1 + dfs(heights));
for (let i = idx; i < idx + size; i++) heights[i] -= size;
}
memo.set(key, res);
return res;
}
return dfs(Array(n).fill(0));
};