铺瓷砖

雨点打透心脏的1/2处 2023-02-16 06:17 108阅读 0赞

一、前言

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

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

二、题目

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

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

示例 1:

20200608203058847.png

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

示例 2

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L25pZTIzMTQ1NTA0NDE_size_16_color_FFFFFF_t_70

示例 3:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L25pZTIzMTQ1NTA0NDE_size_16_color_FFFFFF_t_70 1

  1. 输入:n = 11, m = 13
  2. 输出:6

提示:

1 <= n <= 13

1 <= m <= 13

三、思路

3.1 整体切割

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

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L25pZTIzMTQ1NTA0NDE_size_16_color_FFFFFF_t_70 2

3.2 精细切割

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

1.横切

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L25pZTIzMTQ1NTA0NDE_size_16_color_FFFFFF_t_70 3

2. 竖切

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L25pZTIzMTQ1NTA0NDE_size_16_color_FFFFFF_t_70 4

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

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L25pZTIzMTQ1NTA0NDE_size_16_color_FFFFFF_t_70 5

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

20200608203514226.png

3.3 解决方案

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

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

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

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

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

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

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

四、编码实现

  1. //==========================================================================
  2. /**
  3. * @file : 1240_TilingRectangle.h
  4. * @title: 铺瓷砖
  5. * @purpose : 你是一位施工队的工长,根据设计师的要求准备为一套设计风格独特的房子进行室内装修。
  6. * 房子的客厅大小为 n x m,为保持极简的风格,需要使用尽可能少的 正方形 瓷砖来铺盖地面。
  7. * 假设正方形瓷砖的规格不限,边长都是整数。
  8. * 请你帮设计师计算一下,最少需要用到多少块方形瓷砖?
  9. *
  10. * 来源:力扣(LeetCode)难道:困难
  11. * 链接:https://leetcode-cn.com/problems/tiling-a-rectangle-with-the-fewest-squares
  12. * 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
  13. */
  14. //==========================================================================
  15. #include <vector>
  16. #include <iostream>
  17. #include <algorithm>
  18. using namespace std;
  19. #define NAMESPACE_TILINGRECTANGLE namespace NAME_TILINGRECTANGLE {
  20. #define NAMESPACE_TILINGRECTANGLEEND }
  21. NAMESPACE_TILINGRECTANGLE
  22. // 动态规划—自顶向下
  23. // 不带备忘录
  24. class Solution_1
  25. {
  26. public:
  27. int tilingRectangle(int n, int m)
  28. {
  29. if (n == 0 || m == 0)
  30. return 0;
  31. int minn = min(n, m);
  32. int maxn = max(n, m);
  33. int r = maxn / minn;
  34. int mod = maxn % minn;
  35. if (mod == 0)
  36. return r;
  37. int add = 0;
  38. if (r >= 2)
  39. {
  40. add = r - 1;
  41. maxn = minn + mod;
  42. }
  43. int ret = 0x7FFFFFFF;
  44. // 横切
  45. for (int i = 1; i <= minn / 2; ++i)
  46. ret = min(ret, tilingRectangle(maxn, i) + tilingRectangle(maxn, minn - i));
  47. // 竖切
  48. for (int i = 1; i <= maxn / 2; ++i)
  49. ret = min(ret, tilingRectangle(minn, i) + tilingRectangle(minn, maxn - i));
  50. // 中间有个1X1的正方形
  51. if (minn >= 5 && minn % 2 == 1 && minn + 2 == maxn)
  52. ret = min(ret, 4 + tilingRectangle(minn / 2 - 1, minn / 2 + 3));
  53. return ret + add;
  54. }
  55. };
  56. // 动态规划—自顶向下
  57. // 带备忘录
  58. class Solution_2
  59. {
  60. public:
  61. int tilingRectangle(int n, int m)
  62. {
  63. vector<vector<int>> dp(1000, vector<int>(1000, 0));
  64. return tilingRectangle(n, m, dp);
  65. }
  66. int tilingRectangle(int n, int m, vector<vector<int>>& dp)
  67. {
  68. int minn = min(n, m);
  69. int maxn = max(n, m);
  70. if (dp[minn][maxn] != 0)
  71. {
  72. return dp[minn][maxn];
  73. }
  74. if (n == 0 || m == 0)
  75. return 0;
  76. int r = maxn / minn;
  77. int mod = maxn % minn;
  78. if (mod == 0)
  79. {
  80. dp[minn][maxn] = r;
  81. return r;
  82. }
  83. int add = 0;
  84. if (r >= 2)
  85. {
  86. add = r - 1;
  87. maxn = minn + mod;
  88. }
  89. int ret = 0x7FFFFFFF;
  90. // 横切
  91. for (int i = 1; i <= minn / 2; ++i)
  92. ret = min(ret, tilingRectangle(maxn, i, dp) + tilingRectangle(maxn, minn - i, dp));
  93. // 竖切
  94. for (int i = 1; i <= maxn / 2; ++i)
  95. ret = min(ret, tilingRectangle(minn, i, dp) + tilingRectangle(minn, maxn - i, dp));
  96. // 中间有个1X1的正方形
  97. if (minn >= 5 && minn % 2 == 1 && minn + 2 == maxn)
  98. ret = min(ret, 4 + tilingRectangle(minn / 2 - 1, minn / 2 + 3, dp));
  99. dp[min(n, m)][max(n, m)] = ret + add;
  100. return ret + add;
  101. }
  102. };
  103. // 动态规划—自低向上
  104. class Solution_3
  105. {
  106. public:
  107. int tilingRectangle(int n, int m)
  108. {
  109. vector<vector<int>> dp(n+1, vector<int>(m+1, 0));
  110. for (int i = 1; i <= n; ++i)
  111. {
  112. for (int j = 1; j <= m; ++j)
  113. {
  114. dp[i][j] = INT_MAX;
  115. if (i == j)
  116. {
  117. dp[i][j] = 1;
  118. continue;
  119. }
  120. for (int r = 1; r <= i / 2; ++r)
  121. dp[i][j] = min(dp[i][j], dp[r][j] + dp[i - r][j]);
  122. for (int c = 1; c <= j / 2; ++c)
  123. dp[i][j] = min(dp[i][j], dp[i][c] + dp[i][j - c]);
  124. // 中间有个1X1的正方形
  125. int minn = min(i, j);
  126. int maxn = max(i, j);
  127. if (minn >= 5 && minn % 2 == 1 && minn + 2 == maxn)
  128. dp[i][j] = min(dp[i][j], 4 + dp[minn / 2 - 1][minn / 2 + 3]);
  129. }
  130. }
  131. return dp[n][m];
  132. }
  133. };
  134. // 网上解题方法
  135. // 动态规划—自低向上
  136. class Solution_4
  137. {
  138. public:
  139. int tilingRectangle(int n, int m)
  140. {
  141. if (max(n, m) == 13 && min(n, m) == 11) return 6;
  142. vector<vector<int>> dp(n + 1, vector<int>(m + 1));
  143. for (int i = 1; i <= n; ++i)
  144. for (int j = 1; j <= m; ++j)
  145. {
  146. dp[i][j] = INT_MAX;
  147. if (i == j)
  148. {
  149. dp[i][j] = 1;
  150. continue;
  151. }
  152. for (int r = 1; r <= i / 2; ++r)
  153. dp[i][j] = min(dp[i][j], dp[r][j] + dp[i - r][j]);
  154. for (int c = 1; c <= j / 2; ++c)
  155. dp[i][j] = min(dp[i][j], dp[i][c] + dp[i][j - c]);
  156. }
  157. return dp[n][m];
  158. }
  159. };
  160. // 网上解题方法
  161. // 动态规划—自低向上
  162. class Solution_5
  163. {
  164. public:
  165. int tilingRectangle(int n, int m)
  166. {
  167. vector<vector<int>> dp(n+1, vector<int>(m+1,0));
  168. for (int i = 1; i <= n; i++)
  169. {
  170. for (int j = 1; j <= m; j++)
  171. {
  172. dp[i][j] = INT_MAX;
  173. if (i == j)
  174. {
  175. dp[i][j] = 1;
  176. continue;
  177. }
  178. // 横切
  179. for (int r = 1; r <= i / 2; r++)
  180. dp[i][j] = min(dp[i][j], dp[r][j] + dp[i - r][j]);
  181. // 竖切
  182. for (int c = 1; c <= j / 2; c++)
  183. dp[i][j] = min(dp[i][j], dp[i][c] + dp[i][j - c]);
  184. // 中心一个单元格
  185. for (int p = 1; p <= i; p++)
  186. {
  187. for (int q = 1; q <= j; q++)
  188. {
  189. 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]);
  190. }
  191. }
  192. }
  193. }
  194. return dp[n][m];
  195. }
  196. };
  197. //
  198. // 测试 用例 START
  199. void test(const char* testName, int n, int m, int expect)
  200. {
  201. //Solution_1 S1;
  202. Solution_2 S2;
  203. Solution_3 S3;
  204. Solution_4 S4;
  205. Solution_5 S5;
  206. int result2 = S2.tilingRectangle(n, m);
  207. int result3 = S3.tilingRectangle(n, m);
  208. int result4 = S4.tilingRectangle(n, m);
  209. int result5 = S5.tilingRectangle(n, m);
  210. // 粗略验证一下
  211. if (result2 == result3 && result2 == result4 && result2 == result5)
  212. {
  213. cout << testName << ", solution passed." << endl;
  214. }
  215. else
  216. {
  217. cout << testName << ", solution failed. result2:" << result2 << ", result3:" << result3 << ", result4:" << result4 << ", result5:" << result5 << endl;
  218. }
  219. }
  220. // 测试用例
  221. void Test1()
  222. {
  223. int n = 1;
  224. int m = 1;
  225. int expect = 1;
  226. test("Test1()", n, m, expect);
  227. }
  228. void Test2()
  229. {
  230. int n = 1;
  231. int m = 2;
  232. int expect = 2;
  233. test("Test2()", n, m, expect);
  234. }
  235. void Test3()
  236. {
  237. int n = 2;
  238. int m = 3;
  239. int expect = 3;
  240. test("Test3()", n, m, expect);
  241. }
  242. void Test4()
  243. {
  244. int n = 13;
  245. int m = 11;
  246. int expect = 6;
  247. test("Test4()", n, m, expect);
  248. }
  249. void Test5()
  250. {
  251. int n = 4;
  252. int m = 6;
  253. int expect = 3;
  254. test("Test5()", n, m, expect);
  255. }
  256. void Test6()
  257. {
  258. int n = 7;
  259. int m = 6;
  260. int expect = 5;
  261. test("Test6()", n, m, expect);
  262. }
  263. void Test7()
  264. {
  265. int n = 5;
  266. int m = 8;
  267. int expect = 5;
  268. test("Test7()", n, m, expect);
  269. }
  270. void Test8()
  271. {
  272. int n = 10;
  273. int m = 9;
  274. int expect = 6;
  275. test("Test8()", n, m, expect);
  276. }
  277. void Test9()
  278. {
  279. int n = 7;
  280. int m = 4;
  281. int expect = 5;
  282. test("Test9()", n, m, expect);
  283. }
  284. void Test10()
  285. {
  286. int n = 12;
  287. int m = 11;
  288. int expect = 7;
  289. test("Test10()", n, m, expect);
  290. }
  291. void Test11()
  292. {
  293. int n = 9;
  294. int m = 7;
  295. int expect = 6;
  296. test("Test11()", n, m, expect);
  297. }
  298. void Test12()
  299. {
  300. int n = 22;
  301. int m = 25;
  302. int expect = 0;
  303. test("Test12()", n, m, expect);
  304. }
  305. void Test13()
  306. {
  307. int n = 30;
  308. int m = 37;
  309. int expect = 7;
  310. test("Test13()", n, m, expect);
  311. }
  312. void Test14()
  313. {
  314. int n = 15;
  315. int m = 16;
  316. int expect = 0;
  317. test("Test14()", n, m, expect);
  318. }
  319. void Test15()
  320. {
  321. int n = 100;
  322. int m = 99;
  323. int expect = 0;
  324. test("Test15()", n, m, expect);
  325. }
  326. void Test16()
  327. {
  328. int n = 200;
  329. int m = 3;
  330. int expect = 0;
  331. test("Test16()", n, m, expect);
  332. }
  333. void Test17()
  334. {
  335. int n = 513;
  336. int m = 899;
  337. int expect = 0;
  338. test("Test17()", n, m, expect);
  339. }
  340. NAMESPACE_TILINGRECTANGLEEND
  341. // 测试 用例 END
  342. //
  343. void TilingRectangle_Test()
  344. {
  345. NAME_TILINGRECTANGLE::Test1();
  346. NAME_TILINGRECTANGLE::Test2();
  347. NAME_TILINGRECTANGLE::Test3();
  348. NAME_TILINGRECTANGLE::Test4();
  349. NAME_TILINGRECTANGLE::Test5();
  350. NAME_TILINGRECTANGLE::Test6();
  351. NAME_TILINGRECTANGLE::Test7();
  352. NAME_TILINGRECTANGLE::Test8();
  353. NAME_TILINGRECTANGLE::Test9();
  354. NAME_TILINGRECTANGLE::Test10();
  355. NAME_TILINGRECTANGLE::Test11();
  356. NAME_TILINGRECTANGLE::Test12();
  357. NAME_TILINGRECTANGLE::Test13();
  358. NAME_TILINGRECTANGLE::Test14();
  359. //NAME_TILINGRECTANGLE::Test15();
  360. //NAME_TILINGRECTANGLE::Test16(); // 测试时间较长
  361. //NAME_TILINGRECTANGLE::Test17(); // 测试时间较长
  362. }

执行结果:

20200608203639917.png

发表评论

表情:
评论列表 (有 0 条评论,108人围观)

还没有评论,来说两句吧...

相关阅读

    相关 算法训练 - 瓷砖放 有一长度为N(1<=N<=10)的地板,给定两种不同瓷砖:一种长度为1,另一种长度为2,数目不限。要将这个长度为N的地板满,一共有多少种不同的法?

    问题描述 有一长度为N(1<=N<=10)的地板,给定两种不同瓷砖:一种长度为1,另一种长度为2,数目不限。要将这个长度为N的地板铺满,一共有多少种不同的铺法?   

    相关 地毯

    [https://vijos.org/p/1736][https_vijos.org_p_1736] 描述 为了准备一个独特的颁奖典礼,组织者在会场的一片矩形区域(

    相关 算法训练 瓷砖

    问题描述   有一长度为N(1<=N<=10)的地板,给定两种不同瓷砖:一种长度为1,另一种长度为2,数目不限。要将这个长度为N的地板铺满,一共有多少种不同的铺法?   

    相关 骨牌方格

    Problem Description 在2×n的一个长方形方格中,用一个1× 2的骨牌铺满方格,输入n ,输出铺放方案的总数. 例如n=3时,为2× 3方格,骨牌的铺放

    相关 骨牌方格

    1.知识点:递推 2.题意:在2×n的一个长方形方格中,用一个1× 2的骨牌铺满方格(可横铺或竖铺),输入n ,输出铺放方案的总数 3.递推关系方程:a\[i\] =

    相关 PCB

    问:为何要铺铜? 答:一般铺铜有几个方面原因。 1、EMC.对于大面积的地或电源铺铜,会起到屏蔽作用,有些特殊地,如PGND起到防护作用。 2、PCB工艺要求。一般