一、前言
問題來源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(); // 測試時間較長
}
執行結果: