1. 程式人生 > 其它 >[LeetCode] 1240. Tiling a Rectangle with the Fewest Squares 鋪瓷磚

[LeetCode] 1240. Tiling a Rectangle with the Fewest Squares 鋪瓷磚


Given a rectangle of sizenxm, returnthe minimum number of integer-sided squares that tile the rectangle.

Example 1:

Input: n = 2, m = 3
Output: 3
Explanation: `3` squares are necessary to cover the rectangle.
`2` (squares of `1x1`)
`1` (square of `2x2`)

Example 2:

Input: n = 5, m = 8
Output: 5

Example 3:

Input: n = 11, m = 13
Output: 6

Constraints:

  • 1 <= n, m <= 13

這道題給了一個 n by m 大小的矩形,問最少可以用多少個正方形填滿這個矩形。有點像小時候玩的拼圖遊戲,用大小不同正方形來拼出一個大的矩形。為了方便分析,這裡始終認為n小於等於m,若給定的n大於m,直接調換一下也不影響最終結果。現在來考慮一下如何才能拼出 n by m 大小的矩形,可能大家會下意識的先拿出一個 n by n 的矩形佔一大塊,然後再拿小矩形去拼 n by (m-n) 的矩形,這種類似貪婪演算法的拼法並不能保證全域性最優,這種方法在例子1和2中是可以的,但是例子3中就不適用了。由於沒有明確的策略去拼,為了得到全域性最優解,只有遍歷所有情況,取其中的最小值。則第一個正方形可選的範圍就是 [1, n],對於其中任意一個值i來說,相當於左下角先放了個 i by i 的正方形,剩下的部分可以分為兩個矩形,有兩種不同的分法:水平切一刀的話,就分成了 (n-i) by m 的矩形和 i by (m-i) 的矩形;豎直切一刀的話,就分成了 (n-i) by i 的矩形和 n by (m-i) 的矩形,這兩種分法都要分別計算一下,參見簡陋的下圖所示:


         m
   --------------
   |n-i         |
 n |------------|
   | i |   m-i  |
   --------------

         m
   --------------
   |n-i|        |
 n |----        |
   | i |   m-i  |
   --------------

由於分割成的子矩形可以看作是一個子問題的重現,所以這道題用遞迴來做是非常合適的,同時為了避免大量的重複計算,應該使用記憶陣列來儲存計算過的值,其中 memo[i][j] 就表示 i by j 的矩形可以用最少的正方形拼出的個數。上面這種分割方法並不能包含所有的情況,比如例子3就無法通過這種方法得到。所以還有一種拼法,是同時在左下角和右上角各放一個正方形,然後再去拼剩餘的部分,左下角的正方形邊長為i,範圍是 [1, n],右上角的正方形邊長為j,範圍是 [n-i+1, min(m-i, n)],這兩個邊角正方形大小確定了之後,剩餘的部分可能需要分成三個矩形,就像例子3中所示一樣,中間還有個迷你矩形,其長寬需要特別計算一下,是個 (i+j-n) by (m-i-j) 的矩形,然後剩下的兩個矩形大小分別為 (n-i) by (m-j) 和 (n-j) by (m-i)。這些都理清了之後,程式碼應該也就不難寫了。

主要來看遞迴函式的寫法吧,首先判斷n和m的大小,若n大於m,則交換兩個引數。若n等於0,直接返回0,若n等於m,本身就是個正方形,返回1,若n等於1,則只能用 1 by 1 的正方形來拼,返回m,若 memo[n][m] 值大於0,說明當前情況已經計算過了,直接返回 memo[n][m]。否則開始正式計算,初始化結果 res 為整型最大值,然後遍歷左下角先拼的正方的邊長,之前說了,範圍是 [1, n],然後先計算分成兩個矩形的兩種情況,分別呼叫遞迴,並更新結果 res。然後就是計算右上角再放正方形的情況,其邊長範圍是 [n-i+1, min(m-i, n)],之前也分析過了,然後對分割出的三個小矩形分別呼叫遞迴,並用結果來更新 res 即可,參見程式碼如下:


class Solution {
public:
    int tilingRectangle(int n, int m) {
        if (n > m) return tilingRectangle(m, n);
        vector<vector<int>> memo(n + 1, vector<int>(m + 1));
        return helper(n, m, memo);
    }
    int helper(int n, int m, vector<vector<int>>& memo) {
        if (n > m) return helper(m, n, memo);
        if (n == 0) return 0;
        if (n == m) return 1;
        if (n == 1) return m;
        if (memo[n][m] > 0) return memo[n][m];
        int res = INT_MAX;
        for (int i = 1; i <= n; ++i) {
            res = min(res, 1 + helper(n - i, m, memo) + helper(i, m - i, memo));
            res = min(res, 1 + helper(n, m - i, memo) + helper(n - i, i, memo));
            for (int j = n - i + 1; j < m - i && j < n; ++j) {
                res = min(res, 2 + helper(n - i, m - j, memo) + helper(i + j - n, m - i - j, memo) + helper(n - j, m - i, memo));
            }
        }
        return memo[n][m] = res;
    }
};

Github 同步地址:

https://github.com/grandyang/leetcode/issues/1240


參考資料:

https://leetcode.com/problems/tiling-a-rectangle-with-the-fewest-squares/

https://leetcode.com/problems/tiling-a-rectangle-with-the-fewest-squares/discuss/967635/Java-100-Recursion-with-Memoization.

https://leetcode.com/problems/tiling-a-rectangle-with-the-fewest-squares/discuss/791203/C%2B%2BDynamic-Programming-Backtracking-with-Figure.Explained.(100-faster)


LeetCode All in One 題目講解彙總(持續更新中...)


喜歡請點贊,疼愛請打賞❤️~.~


微信打賞


Venmo 打賞