[LeetCode] 790. Domino and Tromino Tiling 多米诺和三格骨牌

 

You have two types of tiles: a 2 x 1 domino shape and a tromino shape. You may rotate these shapes.

Given an integer n, return the number of ways to tile an 2 x n board. Since the answer may be very large, return it modulo 109 + 7.

In a tiling, every square must be covered by a tile. Two tilings are different if and only if there are two 4-directionally adjacent cells on the board such that exactly one of the tilings has both squares occupied by a tile.

 

Example 1:

Input: n = 3
Output: 5
Explanation: The five different ways are show above.

Example 2:

Input: n = 1
Output: 1

 

Constraints:

  • 1 <= n <= 1000

 

这道题是关于多米诺骨牌和三格骨牌的,其中由两个方形格子组成的是多米诺骨牌(音译,即为双格骨牌),而由三个方形格子组成的‘L’型的是三格骨牌,但其实本质还是个拼格子的问题,并没有利用到骨牌酷炫的连倒技能,倒反而更像是俄罗斯方块中的形状。说是有一个2xN大小的棋盘,我们需要用这些多米诺和三格骨牌来将棋盘填满,问有多少种不同的填充方法,结果需要对一个超大数取余。那么根据博主多年的经验,对于这种求极值,并且超大的情况下,只能使用动态规划 Dynamic Programming 来做,什么暴力递归神马的,等着爆栈吧。

既然决定了要用 DP 来做,那么首先就来设计 dp 数组吧,这里就用一个一维的 dp 数组就行了,其中 dp[i] 表示填满前i列的不同填法总数对超大数 10e^9+7 取余后的结果。DP 解法的难点就是求状态转移方程了,没什么太好的思路的时候,就从最简单的情况开始罗列吧。题目中给了N的范围是 [1, 1000],那么来看:

当 N=1 时,那么就是一个 2x1 大小的棋盘,只能放一个多米诺骨牌,只有一种情况。

当 N=2 时,那么就是一个 2x2 大小的棋盘,如下图所示,有两种放置方法,可以将两个多米诺骨牌竖着并排放,或者是将其横着并排放。

当 N=3 时,那么就是一个 3x2 大小的棋盘,共用五种放置方法,如下图所示。仔细观察这五种情况,可以发现其时时跟上面的情况有联系的。前两种情况其实是 N=2 的两种情况后面加上了一个竖着的多米诺骨牌,第三种情况其实是 N=1 的那种情况后面加上了两个平行的横向的多米诺骨牌,后两种情况是 N=0(空集)再加上两种三格骨牌对角摆开的情况。

当 N=4 时,那么就是一个 4x2 大小的棋盘,共用十一种放置方法,太多了就不一一画出来了,但是其也是由之前的情况组合而成的。首先是 N=3 的所有情况后面加上一个竖着多米诺骨牌,然后是 N=2 的所有情况加上两个平行的横向的多米诺骨牌,然后 N=1 再加上两种三格骨牌对角摆开的情况,然后 N=0(空集)再加上两种三格骨牌和一个横向多米诺骨牌组成的情况。

N=5 的情况博主没有再画了,可以参见ZhengKaiWei大神的帖子中的手稿图,很萌~

根据目前的状况,我们可以总结一个很重要的规律,就是 dp[n] 是由之前的 dp 值组成的,其中 dp[n-1] 和 dp[n-2] 各自能贡献一种组成方式,而 dp[n-3],一直到 dp[0],都能各自贡献两种组成方式,所以状态转移方程呼之欲出:

dp[n] = dp[n-1] + dp[n-2] + 2 * (dp[n-3] + ... + dp[0])

        = dp[n-1] + dp[n-3] + dp[n-2] + dp[n-3] + 2 * (dp[n-4] + ... dp[0])

        = dp[n-1] + dp[n-3] + dp[n-1]

        = 2 * dp[n-1] + dp[n-3]

最后化简后的形式就是最终的状态转移方程了,是不是叼的飞起~

 

 

class Solution {
public:
    int numTilings(int N) {
        if (N < 2) return 1;
        int M = 1e9 + 7;
        vector<long> dp(N + 1);
        dp[0] = 1; dp[1] = 1; dp[2] = 2;
        for (int i = 3; i <= N; ++i) {
            dp[i] = (dp[i - 1] * 2 + dp[i - 3]) % M;
        }
        return dp[N];
    }
};

 

Github 同步地址:

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

 

参考资料:

https://leetcode.com/problems/domino-and-tromino-tiling/

https://leetcode.com/problems/domino-and-tromino-tiling/discuss/116513/Java-solution-DP

https://leetcode.com/problems/domino-and-tromino-tiling/discuss/116581/Detail-and-explanation-of-O(n)-solution-why-dpn2*dn-1+dpn-3

https://leetcode.com/problems/domino-and-tromino-tiling/discuss/116664/Schematic-explanation-of-two-equivalent-DP-recurrence-formula

 

LeetCode All in One 题目讲解汇总(持续更新中...)

posted @ 2018-06-13 19:58  Grandyang  阅读(5593)  评论(1编辑  收藏  举报
Fork me on GitHub