铺瓷砖

本文详细解析了LeetCode 1240题目的解决方案,探讨了如何使用最少数量的正方形瓷砖覆盖一个矩形区域。文章介绍了多种动态规划策略,包括自顶向下和自底向上的方法,并提供了代码实现和测试用例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、前言

问题来源LeetCode 1240,难度:困难

问题链接:https://leetcode-cn.com/problems/tiling-a-rectangle-with-the-fewest-squares

 

二、题目

你是一位施工队的工长,根据设计师的要求准备为一套设计风格独特的房子进行室内装修。

房子的客厅大小为 n x m,为保持极简的风格,需要使用尽可能少的 正方形 瓷砖来铺盖地面。假设正方形瓷砖的规格不限,边长都是整数。请你帮设计师计算一下,最少需要用到多少块方形瓷砖?

示例 1:

输入:n = 2, m = 3
输出:3
解释:3 块地砖就可以铺满卧室。
     2 块 1x1 地砖
     1 块 2x2 地砖

示例 2

示例 3:

输入:n = 11, m = 13
输出:6

提示:

1 <= n <= 13

1 <= m <= 13

 

三、思路

3.1 整体切割

我们先来看这种情况,当矩形(长m,宽n),如果m 远大于n(m大于n两倍以上),我们可以直接将其逐渐切割下一个 nxn 的矩形,此时剩下的 长如果还是n两倍以上继续切割,切割到什么时候为止呢,m >= n+1 && m <= 2n-1。 贪心算法思路。

3.2 精细切割

经过整体切割之后,剩下的矩形(长n,宽m),m >= n+1 && m <= 2n-1。可以用三种切割方法

1.横切

2. 竖切

3. 组合切割,中间会有一个1X1的正方形。

前两种好理解,最难理解的是最后一种情况,最后一种情况我们可以用暴力法确定这个正方形可能在任意一个指标上。这里提供一种方法最优确定这个正方形方法。如上图 n = 2L + 1, m = 2L + 3,即:m = n + 2 (n >= 5 && n为偶数)。为什么这种长宽下的矩形中,包含一个1X1的矩形可能是最小切割呢?我也证明不出来,只是在画图并用贪心得最大正方形的时候经过公式推导出来的这个公式。在leetcode中验证是可以通过,执行速度很快。

3.3 解决方案

提供用5个解决方法,后面两种是leetcode中别人的解决方法。

方法一:动态规划—自顶向下,不带备忘录

方法二:动态规划—自顶向下,带备忘录。(先做了一次贪心算法,在宽较小,长很大的时候效率更好)

方法三:动态规划—自低向上

方法四:动态规划—自低向上,网上解题方法,用于验证

方法五:动态规划—自低向上,网上解题方法,用于验证

需要说明:leetcode中测试用例并不够全面,在测试过程中发现自己写的一种解决方案通过了测试,但是在本地测试增加测试用例的时候结果和方式四、方法输出结果不一样。

 

四、编码实现

//==========================================================================
/**
* @file : 1240_TilingRectangle.h
* @title: 铺瓷砖
* @purpose : 你是一位施工队的工长,根据设计师的要求准备为一套设计风格独特的房子进行室内装修。
* 房子的客厅大小为 n x m,为保持极简的风格,需要使用尽可能少的 正方形 瓷砖来铺盖地面。
* 假设正方形瓷砖的规格不限,边长都是整数。
* 请你帮设计师计算一下,最少需要用到多少块方形瓷砖?
*
* 来源:力扣(LeetCode)难道:困难
* 链接:https://leetcode-cn.com/problems/tiling-a-rectangle-with-the-fewest-squares
* 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
*/
//==========================================================================

#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;

#define NAMESPACE_TILINGRECTANGLE namespace NAME_TILINGRECTANGLE {
#define NAMESPACE_TILINGRECTANGLEEND }
NAMESPACE_TILINGRECTANGLE

// 动态规划—自顶向下
// 不带备忘录
class Solution_1
{
public:
	int tilingRectangle(int n, int m)
	{
		if (n == 0 || m == 0)
			return 0;

		int minn = min(n, m);
		int maxn = max(n, m);

		int r = maxn / minn;
		int mod = maxn % minn;
		if (mod == 0)
			return r;

		int add = 0;
		if (r >= 2)
		{
			add = r - 1;
			maxn = minn + mod;
		}

		int ret = 0x7FFFFFFF;
		// 横切
		for (int i = 1; i <= minn / 2; ++i)
			ret = min(ret, tilingRectangle(maxn, i) + tilingRectangle(maxn, minn - i));
		

		// 竖切
		for (int i = 1; i <= maxn / 2; ++i)
			ret = min(ret, tilingRectangle(minn, i) + tilingRectangle(minn, maxn - i));
		

		// 中间有个1X1的正方形
		if (minn >= 5 && minn % 2 == 1 && minn + 2 == maxn)
			ret = min(ret, 4 + tilingRectangle(minn / 2 - 1, minn / 2 + 3));

		return ret + add;
	}
};

// 动态规划—自顶向下
// 带备忘录
class Solution_2
{
public:
	int tilingRectangle(int n, int m)
	{
		vector<vector<int>> dp(1000, vector<int>(1000, 0));
		return tilingRectangle(n, m, dp);
	}

	int tilingRectangle(int n, int m, vector<vector<int>>& dp)
	{
		int minn = min(n, m);
		int maxn = max(n, m);

		if (dp[minn][maxn] != 0)
		{
			return dp[minn][maxn];
		}

		if (n == 0 || m == 0)
			return 0;

		int r = maxn / minn;
		int mod = maxn % minn;
		if (mod == 0)
		{
			dp[minn][maxn] = r;
			return r;
		}
		int add = 0;
		if (r >= 2)
		{
			add = r - 1;
			maxn = minn + mod;
		}

		int ret = 0x7FFFFFFF;

		// 横切
		for (int i = 1; i <= minn / 2; ++i)
			ret = min(ret, tilingRectangle(maxn, i, dp) + tilingRectangle(maxn, minn - i, dp));

		// 竖切
		for (int i = 1; i <= maxn / 2; ++i)
			ret = min(ret, tilingRectangle(minn, i, dp) + tilingRectangle(minn, maxn - i, dp));

		// 中间有个1X1的正方形
		if (minn >= 5 && minn % 2 == 1 && minn + 2 == maxn)
			ret = min(ret, 4 + tilingRectangle(minn / 2 - 1, minn / 2 + 3, dp));

		dp[min(n, m)][max(n, m)] = ret + add;
		return ret + add;
	}
};

// 动态规划—自低向上
class Solution_3
{
public:
	int tilingRectangle(int n, int m)
	{
		vector<vector<int>> dp(n+1, vector<int>(m+1, 0));
		for (int i = 1; i <= n; ++i)
		{
			for (int j = 1; j <= m; ++j)
			{
				dp[i][j] = INT_MAX;
				if (i == j)
				{
					dp[i][j] = 1;
					continue;
				}

				for (int r = 1; r <= i / 2; ++r)
					dp[i][j] = min(dp[i][j], dp[r][j] + dp[i - r][j]);

				for (int c = 1; c <= j / 2; ++c)
					dp[i][j] = min(dp[i][j], dp[i][c] + dp[i][j - c]);

				// 中间有个1X1的正方形 
				int minn = min(i, j);
				int maxn = max(i, j);
				if (minn >= 5 && minn % 2 == 1 && minn + 2 == maxn)
					dp[i][j] = min(dp[i][j], 4 + dp[minn / 2 - 1][minn / 2 + 3]);
			}
		}

		return dp[n][m];
	}
};

// 网上解题方法
// 动态规划—自低向上
class Solution_4
{
public:
    int tilingRectangle(int n, int m)
    {
        if (max(n, m) == 13 && min(n, m) == 11) return 6;
        vector<vector<int>> dp(n + 1, vector<int>(m + 1));
        for (int i = 1; i <= n; ++i)
            for (int j = 1; j <= m; ++j)
            {
                dp[i][j] = INT_MAX;
                if (i == j)
                {
                    dp[i][j] = 1;
                    continue;
                }
                for (int r = 1; r <= i / 2; ++r)
                    dp[i][j] = min(dp[i][j], dp[r][j] + dp[i - r][j]);
                for (int c = 1; c <= j / 2; ++c)
                    dp[i][j] = min(dp[i][j], dp[i][c] + dp[i][j - c]);
            }
        return dp[n][m];
    }
};

// 网上解题方法
// 动态规划—自低向上
class Solution_5 
{
public:
	int tilingRectangle(int n, int m) 
	{
		vector<vector<int>> dp(n+1, vector<int>(m+1,0));
		for (int i = 1; i <= n; i++)
		{
			for (int j = 1; j <= m; j++)
			{
				dp[i][j] = INT_MAX;
				if (i == j) 
				{
					dp[i][j] = 1;
					continue;
				}
				// 横切
				for (int r = 1; r <= i / 2; r++) 
					dp[i][j] = min(dp[i][j], dp[r][j] + dp[i - r][j]);

				// 竖切
				for (int c = 1; c <= j / 2; c++) 
					dp[i][j] = min(dp[i][j], dp[i][c] + dp[i][j - c]);
				
				// 中心一个单元格
				for (int p = 1; p <= i; p++)
				{
					for (int q = 1; q <= j; q++) 
					{
						dp[i][j] = min(dp[i][j], 1 + dp[p - 1][q] + dp[i - p + 1][q - 1] + dp[i - p][j - q + 1] + dp[p][j - q]);
					}
				}
					
			}
		}

		return dp[n][m];
	}
};

//////////////////////////////////////////////////////////////////////
// 测试 用例 START
void test(const char* testName, int n, int m, int expect)
{
	//Solution_1 S1;
	Solution_2 S2;
	Solution_3 S3;
	Solution_4 S4;
	Solution_5 S5;

	int result2 = S2.tilingRectangle(n, m);
    int result3 = S3.tilingRectangle(n, m);
	int result4 = S4.tilingRectangle(n, m);
	int result5 = S5.tilingRectangle(n, m);

    // 粗略验证一下
	if (result2 == result3 && result2 == result4 && result2 == result5)
	{
		cout << testName << ", solution passed." << endl;
	}
	else
	{
		cout << testName << ", solution failed. result2:" << result2 << ", result3:" << result3 << ", result4:" << result4 << ", result5:" << result5 << endl;
	}
}

// 测试用例
void Test1()
{
    int n = 1;
    int m = 1;
    int expect = 1;

    test("Test1()", n, m, expect);
}

void Test2()
{
    int n = 1;
    int m = 2;
    int expect = 2;

    test("Test2()", n, m, expect);
}

void Test3()
{
    int n = 2;
    int m = 3;
    int expect = 3;

    test("Test3()", n, m, expect);
}

void Test4()
{
    int n = 13;
    int m = 11;
    int expect = 6;

    test("Test4()", n, m, expect);
}

void Test5()
{
    int n = 4;
    int m = 6;
    int expect = 3;

    test("Test5()", n, m, expect);
}

void Test6()
{
    int n = 7;
    int m = 6;
    int expect = 5;

    test("Test6()", n, m, expect);
}

void Test7()
{
    int n = 5;
    int m = 8;
    int expect = 5;

    test("Test7()", n, m, expect);
}

void Test8()
{
    int n = 10;
    int m = 9;
    int expect = 6;

    test("Test8()", n, m, expect);
}

void Test9()
{
	int n = 7;
	int m = 4;
	int expect = 5;

	test("Test9()", n, m, expect);
}

void Test10()
{
	int n = 12;
	int m = 11;
	int expect = 7;

	test("Test10()", n, m, expect);
}

void Test11()
{
	int n = 9;
	int m = 7;
	int expect = 6;

	test("Test11()", n, m, expect);
}

void Test12()
{
	int n = 22;
	int m = 25;
	int expect = 0;

	test("Test12()", n, m, expect);
}

void Test13()
{
	int n = 30;
	int m = 37;
	int expect = 7;

	test("Test13()", n, m, expect);
}

void Test14()
{
	int n = 15;
	int m = 16;
	int expect = 0;

	test("Test14()", n, m, expect);
}

void Test15()
{
	int n = 100;
	int m = 99;
	int expect = 0;

	test("Test15()", n, m, expect);
}

void Test16()
{
	int n = 200;
	int m = 3;
	int expect = 0;

	test("Test16()", n, m, expect);
}

void Test17()
{
	int n = 513;
	int m = 899;
	int expect = 0;

	test("Test17()", n, m, expect);
}

NAMESPACE_TILINGRECTANGLEEND
// 测试 用例 END
//////////////////////////////////////////////////////////////////////

void TilingRectangle_Test()
{
    NAME_TILINGRECTANGLE::Test1();
    NAME_TILINGRECTANGLE::Test2();
    NAME_TILINGRECTANGLE::Test3();
    NAME_TILINGRECTANGLE::Test4();
    NAME_TILINGRECTANGLE::Test5();
    NAME_TILINGRECTANGLE::Test6();
    NAME_TILINGRECTANGLE::Test7();
    NAME_TILINGRECTANGLE::Test8();
    NAME_TILINGRECTANGLE::Test9();
    NAME_TILINGRECTANGLE::Test10();
    NAME_TILINGRECTANGLE::Test11();
    NAME_TILINGRECTANGLE::Test12();
    NAME_TILINGRECTANGLE::Test13();
    NAME_TILINGRECTANGLE::Test14();
    //NAME_TILINGRECTANGLE::Test15();
    //NAME_TILINGRECTANGLE::Test16();	// 测试时间较长
    //NAME_TILINGRECTANGLE::Test17();	// 测试时间较长
}

执行结果:

 

 

 

### LeetCode 瓷砖算法题目解决方案 #### 问题描述 对于给定尺寸的矩形区域,使用不同大小的地砖设整个区域。目标是计算完成设所需的最少地砖数量。 #### 方案分析 针对此类问题的一种有效方法是利用动态规划(DP),其核心在于构建状态转移方程并初始化边界条件。具体到本题场景下: - 定义 `dp[i][j]` 表示前 i 行 j 列已经被完全覆盖的情况下所需最小地砖数目。 - 对于每一个格子 `(i,j)` ,考虑两种情况: - 如果当前位置为空,则尝试放置一块合适大小的地砖,并更新相应的 dp 值; - 否则跳过该位置继续处理下一个未被占用的位置。 为了简化实现过程,在实际编码时通常会引入辅助函数用于枚举所有可能形状的地砖组合方式,并据此调整当前的状态值。 考虑到输入规模较小(n=2,m=3), 可以直接穷尽所有可能性来求解最优解[^2]. ```python def numTilings(N): MOD = int(1e9+7) @lru_cache(None) def dfs(i, status): if i >= N: return not status res = (dfs(i + 1, status) * (status == 0 or status == 3)) % MOD for s in range((not bool(status & 1)) << 1 | (not bool(status & 2))): res += dfs(i + 1, ((s ^ 3) & status) | (((~s)&1)<<1|(s&1))) return res % MOD return dfs(0, 0) ``` 此代码片段展示了如何运用记忆化搜索技术优化暴力破解的方法。这里定义了一个递归函数 `dfs()` 来模拟每一步的选择过程;并通过位运算技巧高效表示每一行的状态变化规律。最终结果存在一个全局变量中以便后续调用查询。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值