> For the complete documentation index, see [llms.txt](https://leetcode-solution-leetcode-pp.gitbook.io/leetcode-solution/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://leetcode-solution-leetcode-pp.gitbook.io/leetcode-solution/medium/790.domino-and-tromino-tiling.md).

# 0790. 多米诺和托米诺平铺

### 题目地址(790. 多米诺和托米诺平铺)

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

### 题目描述

```
有两种形状的瓷砖：一种是 2x1 的多米诺形，另一种是形如 "L" 的托米诺形。两种形状都可以旋转。

XX  <- 多米诺

XX  <- "L" 托米诺
X


给定 N 的值，有多少种方法可以平铺 2 x N 的面板？返回值 mod 10^9 + 7。

（平铺指的是每个正方形都必须有瓷砖覆盖。两个平铺不同，当且仅当面板上有四个方向上的相邻单元中的两个，使得恰好有一个平铺有一个瓷砖占据两个正方形。）

示例:
输入: 3
输出: 5
解释:
下面列出了五种不同的方法，不同字母代表不同瓷砖：
XYZ XXZ XYY XXY XYY
XYZ YYZ XZZ XYY XXY

提示：

N  的范围是 [1, 1000]

 
```

### 前置知识

* 动态规划

### 公司

* 暂无

### 思路

这种题目和铺瓷砖一样，这种题目基本都是动态规划可解。做这种题目的诀窍就是将所有的可能都列举出来，然后分析问题的最优子结构。最后根据子问题之间的递推关系解决问题。

如果题目只有 XX 型，或者只有 L 型，实际上就很简单了。而这道题是 XX 和 L，则稍微有点难度。力扣还有一些其他更复杂度的铺瓷砖，都是给你若干瓷砖，让你刚好铺满一个形状。大家做完这道题之后可以去尝试一下其他题相关目。

以这道题来说，所有可能的情况无非就是以下 6 种：

![](https://p.ipic.vip/9wyy2o.jpg)

![](https://p.ipic.vip/q04uxe.jpg)

而题目要求的是**刚好铺满** 2 \* N 的情况的总的可能数。

如上图 1，2，3，5 可能是刚好铺满 2 \_ N 的瓷砖的**最后一块砖**，换句话说 4 和 6 不能是刚好铺满 2 \_ N 的最后一块瓷砖。

为了方便描述，我们令 F(n) 表示刚好铺满 2 \* n 的瓷砖的总的可能数，因此题目要求的其实就是 F(n)。

* 如果最后一块选择了形状 1，那么**此时**的刚好铺满 2 \* n 的瓷砖的总的可能数是 F(n-2)
* 如果最后一块选择了形状 2，那么**此时**的刚好铺满 2 \* n 的瓷砖的总的可能数是 F(n-1)
* 如果最后一块选择了形状 3，那么**此时**的刚好铺满 2 \* n 的瓷砖的总的可能数是 ?
* 如果最后一块选择了形状 5，那么**此时**的刚好铺满 2 \* n 的瓷砖的总的可能数是 ?
* 如果最后一块选择了形状 4 和 6，那么**此时**的刚好铺满 2 \* n 的瓷砖的总的可能数是 0。换句话说 4 和 6 不可能是刚好铺满的最后一块砖。

虽然 4 和 6 不可能是刚好铺满的最后一块砖，但其实可以是中间状态，中间状态可以进一步转移到**刚好铺满的状态**。比如股票问题就是这样，虽然我们的最终答案不可能是买入之后，一定是卖出，但是中间的过程可以卖出，通过卖出转移到最终状态。

现在的问题是如何计算：最后一块选择了形状 3 和 最后一块选择了形状 5 的总的可能数，以及 4 和 6 在什么情况下选择。实际上，我们只需要考虑选择我 3 和 5 的总的可能数就行了，其原因稍后你就知道了。

为了表示所有的情况，我们需要另外一个状态定义和转移。 我们令 T(n) 表示刚好铺满 2 \* (N - 1)瓷砖，最后一列只有一块瓷砖的总的可能数。对应上图中的 4 和 6。

经过这样的定义，那么就有：

* 如果倒数第二块选择了形状 4，那么最后一块选择形状 3 刚好铺满 2 \* N 瓷砖。
* 如果倒数第二块选择了形状 6，那么最后一块选择形状 5 刚好铺满 2 \* N 瓷砖。

> 大家可以根据基本图形画一下试试就知道了。同时你也应该理解了 4 和 6 在什么情况下使用。

根据以上的信息有如下公式:

```
F(n) = F(n-1) + F(n-2) + 2 * T(n-1)
```

由于上述等式有两个变量，因此至少需要两个这样的等式才可解。而上面的等式是 F(n) = xxx，因此一个直觉找到一个类似 T(n) = xxx 的公式。不难发现如下等式：

```
T(n) = 2 * F(n-2) + T(n-1)
```

> 乘以 2 是因为最后一块可以选择 4 或者 6 任意一块

将上面两个公式进行合并。具体来说就是：

```
F(n) = F(n-1) + F(n-2) + 2 * T(n-1) ①
T(n) = 2 * F(n-2) + T(n-1)  ②
将 ② 的 n 替换为 n - 1得：
T(n-1) = 2 * F(n-3) + T(n-2) ③
将 ③ 两侧乘以 2 得：
2 * T(n-1) = 4 * F(n-3) + 2 * T(n-2) ④
```

将 ④ 代入 ① 得：

```
F(n) = F(n-1) + F(n-2) + 4 * F(n-3) + 2 * T(n-2)
将 4*F(n-3) 拆分为 F(n-3) + 3 * F(n-3) 并移动式子顺序得：
F(n) = F(n-2) + F(n-3) + 2 * T(n-2) + F(n-1) + 3 * F(n-3)
将 F(n-2) + F(n-3) + 2 * T(n-2) 替换为 F(n-1) 得：
F(n)= 2 * F(n-1) + F(n-3)
```

至此，我们得出了状态转移方程：

```
F(n) = 2 * F(n-1) + F(n-3)
```

### 关键点

* 识别最优子结构
* 对一块瓷砖能拼成的图形进行分解，并对每一种情况进行讨论

### 代码

* 语言支持：Python3

Python3 Code:

```python

class Solution:
    def numTilings(self, N: int) -> int:
        dp = [0] * (N + 3)
        # f(3) = 2 * f(2) + f(0) = 2 + f(0) = 1 -> f(0) = -1
        # f(4) = 2 * f(3) + f(1) = 2 + f(1) = 2 -> f(1) = 0
        dp[0] = -1
        dp[1] = 0
        dp[2] = 1
        # f(n) = f(n-1) + f(n-2) + 2 * T(n-1)
        # 2 * T(n-1) = 2 * f(n-3) + 2 * T(n-2)
        # f(n) = f(n-1) + 2 * f(n-3) + f(n-2) + 2T(n-2) = f(n-1) + f(n-3) + f(n-3) + f(n-2) + 2T(n-2) = f(n-1) + f(n-3) + f(n-1) = 2 * f(n-1) + f(n-3)
        for i in range(3, N + 3):
            dp[i] = 2 * dp[i-1] + dp[i-3]
        return dp[-1] % (10 ** 9 + 7)

```

**复杂度分析**

令 n 为数组长度。

* 时间复杂度：$O(n)$
* 空间复杂度：$O(n)$

使用滚动数组优化可以将空间复杂度降低到 $O(1)$，大家可以试试。

> 此题解由 [力扣刷题插件](https://leetcode-pp.github.io/leetcode-cheat/?tab=solution-template) 自动生成。

力扣的小伙伴可以[关注我](https://leetcode-cn.com/u/fe-lucifer/)，这样就会第一时间收到我的动态啦\~

以上就是本文的全部内容了。大家对此有何看法，欢迎给我留言，我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库：<https://github.com/azl397985856/leetcode> 。 目前已经 40K star 啦。大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。

关注公众号力扣加加，努力用清晰直白的语言还原解题思路，并且有大量图解，手把手教你识别套路，高效刷题。

![](https://p.ipic.vip/4nga7b.jpg)
