题目描述
简单来讲,就是给定一个 m × n 的矩形,求至少需要多少个正方形,才能恰好拼成这个 m × n 的矩形
从直觉的角度
对于一个 3 × 2 的矩形,直观上我们会选择短边,先放置一个 2 × 2 的正方形,然后剩下一个 1 × 2 的区域。继续选择短边,我们需要一个 1 × 1 的正方形,最终只剩下一个 1 × 1 的正方形,总共需要 3 个正方形,这与样例相符。
这种直觉上的策略,即每一轮都选择短边来形成一个最大的正方形,似乎是可行的。但是,从 13 × 11 的样例中,我们可以看到这种方法的局限性。
直觉上的贪心策略得到的结果是需要 8 个正方形,但最优解是 6 个正方形,具体的划分方案如下:
我们可以看到,最优方案中有一个较小的正方形,由于这种特殊结构,最优解很难通过贪心或动态规划得到。在没有更好的解决方案,且数据量不大(m 和 n 的值小于 20)的情况下,暴力搜索可能是最佳选择。
暴搜方式
如果选择暴力搜索,我们需要考虑如何搜索以覆盖所有可能的方案。
一种方法是固定左上角的点 (i, j),从大到小遍历可能的正方形边长,直到边长为 1,本轮遍历结束:
如果 j > n,说明第 i 行的小正方形已经填满,需要进行 i++,并且 j 重置为 1,从下一行开始遍历未填充的小正方形。通过这种方式,在遍历过程中,我们可能会得到如下方案,其中红色表示当前固定的左上角节点,灰色部分表示已经填充的矩形,蓝色代表还未填充的矩形:
记录节点 (i, j) 是否填充过,只需要一个布尔数组即可。
在填充过程中,只需检查对应节点是否已被填充,如果已经填充过,再次填充会导致矩形重叠,说明当前方案不可行,应立即退出当前轮次的搜索。
状态复原小 tips
由于使用布尔数组存储节点 (i, j) 是否填充过,可以利用位运算,只用一个函数就实现填充或取消填充。
如果 (i, j) 为 false,与 true 进行一次异或操作,可以变为 true,表示进行一次填充操作。若再与 true 进行一次异或操作,就可以复原成 false,表示取消填充。
完整 Java 代码
public class Main {
private static boolean[][] matrix;
private static int x, y;
private static int ans;
private static boolean check(int m, int n, int len) {
for (int i = 0; i < len; i++) {
for (int j = 0; j < len; j++) {
if ( matrix[m + i][n + j] ) {
return false;
}
}
}
return true;
}
private static void fill(int m, int n, int len) {
for (int i = 0; i < len; i++) {
for (int j = 0; j < len; j++) {
matrix[m + i][n + j] ^= true;
}
}
}
private static void dfs(int m, int n, int cnt) {
if(cnt >= ans) {
return;
}
if(m == x + 1) {
ans = cnt;
return;
}
if(n > y) {
dfs(m + 1, 1, cnt);
}
boolean flag = true;
for(int j = n; j <= y; j++) {
// 尝试填充该格子
if( !matrix[m][j] ) {
flag = false;
for(int i = Math.min(y - j + 1, x - m + 1); i >= 1; i--) {
if( check(m, j, i) ) {
// 填充
fill(m, j, i);
dfs(m, n + i, cnt + 1);
// 复原
fill(m, j, i);
}
}
// 当前格子的所有填充情况已考虑
break;
}
}
// 第 m 行的格子都已填充完
if( flag ) {
dfs(m + 1, 1, cnt);
}
}
public static int solution(int m, int n) {
// write code here
x = m;
y = n;
matrix = new boolean[x + 1][y + 1];
ans = Integer.MAX_VALUE;
dfs(1, 1, 0);
return ans;
}
public static void main(String[] args) {
System.out.println(solution(3, 2) == 3);
System.out.println(solution(13, 11) == 6);
System.out.println(solution(4, 4) == 1);
}
}