|
| 1 | +## 题目地址(790. 多米诺和托米诺平铺) |
| 2 | + |
| 3 | +https://leetcode-cn.com/problems/domino-and-tromino-tiling/ |
| 4 | + |
| 5 | +## 题目描述 |
| 6 | + |
| 7 | +``` |
| 8 | +有两种形状的瓷砖:一种是 2x1 的多米诺形,另一种是形如 "L" 的托米诺形。两种形状都可以旋转。 |
| 9 | +
|
| 10 | +XX <- 多米诺 |
| 11 | +
|
| 12 | +XX <- "L" 托米诺 |
| 13 | +X |
| 14 | +
|
| 15 | +
|
| 16 | +给定 N 的值,有多少种方法可以平铺 2 x N 的面板?返回值 mod 10^9 + 7。 |
| 17 | +
|
| 18 | +(平铺指的是每个正方形都必须有瓷砖覆盖。两个平铺不同,当且仅当面板上有四个方向上的相邻单元中的两个,使得恰好有一个平铺有一个瓷砖占据两个正方形。) |
| 19 | +
|
| 20 | +示例: |
| 21 | +输入: 3 |
| 22 | +输出: 5 |
| 23 | +解释: |
| 24 | +下面列出了五种不同的方法,不同字母代表不同瓷砖: |
| 25 | +XYZ XXZ XYY XXY XYY |
| 26 | +XYZ YYZ XZZ XYY XXY |
| 27 | +
|
| 28 | +提示: |
| 29 | +
|
| 30 | +N 的范围是 [1, 1000] |
| 31 | +
|
| 32 | + |
| 33 | +``` |
| 34 | + |
| 35 | +## 前置知识 |
| 36 | + |
| 37 | +- 动态规划 |
| 38 | + |
| 39 | +## 公司 |
| 40 | + |
| 41 | +- 暂无 |
| 42 | + |
| 43 | +## 思路 |
| 44 | + |
| 45 | +这种题目和铺瓷砖一样,这种题目基本都是动态规划可解。做这种题目的诀窍就是将所有的可能都列举出来,然后分析问题的最优子结构。最后根据子问题之间的递推关系解决问题。 |
| 46 | + |
| 47 | +如果题目只有 XX 型,或者只有 L 型,实际上就很简单了。而这道题是 XX 和 L,则稍微有点难度。力扣还有一些其他更复杂度的铺瓷砖,都是给你若干瓷砖,让你刚好铺满一个形状。大家做完这道题之后可以去尝试一下其他题相关目。 |
| 48 | + |
| 49 | +以这道题来说,所有可能的情况无非就是以下 6 种: |
| 50 | + |
| 51 | + |
| 52 | + |
| 53 | + |
| 54 | + |
| 55 | +而题目要求的是**刚好铺满** 2 \* N 的情况的总的可能数。 |
| 56 | + |
| 57 | +如上图 1,2,3,5 可能是刚好铺满 2 _ N 的瓷砖的**最后一块砖**,换句话说 4 和 6 不能是刚好铺满 2 _ N 的最后一块瓷砖。 |
| 58 | + |
| 59 | +为了方便描述,我们令 F(n) 表示刚好铺满 2 \* n 的瓷砖的总的可能数,因此题目要求的其实就是 F(n)。 |
| 60 | + |
| 61 | +- 如果最后一块选择了形状 1,那么**此时**的刚好铺满 2 \* n 的瓷砖的总的可能数是 F(n-2) |
| 62 | +- 如果最后一块选择了形状 2,那么**此时**的刚好铺满 2 \* n 的瓷砖的总的可能数是 F(n-1) |
| 63 | +- 如果最后一块选择了形状 3,那么**此时**的刚好铺满 2 \* n 的瓷砖的总的可能数是 ? |
| 64 | +- 如果最后一块选择了形状 5,那么**此时**的刚好铺满 2 \* n 的瓷砖的总的可能数是 ? |
| 65 | +- 如果最后一块选择了形状 4 和 6,那么**此时**的刚好铺满 2 \* n 的瓷砖的总的可能数是 0。换句话说 4 和 6 不可能是刚好铺满的最后一块砖。 |
| 66 | + |
| 67 | +虽然 4 和 6 不可能是刚好铺满的最后一块砖,但其实可以是中间状态,中间状态可以进一步转移到**刚好铺满的状态**。比如股票问题就是这样,虽然我们的最终答案不可能是买入之后,一定是卖出,但是中间的过程可以卖出,通过卖出转移到最终状态。 |
| 68 | + |
| 69 | +现在的问题是如何计算:最后一块选择了形状 3 和 最后一块选择了形状 5 的总的可能数,以及 4 和 6 在什么情况下选择。实际上,我们只需要考虑选择我 3 和 5 的总的可能数就行了,其原因稍后你就知道了。 |
| 70 | + |
| 71 | +为了表示所有的情况,我们需要另外一个状态定义和转移。 我们令 T(n) 表示刚好铺满 2 \* (N - 1)瓷砖,最后一列只有一块瓷砖的总的可能数。对应上图中的 4 和 6。 |
| 72 | + |
| 73 | +经过这样的定义,那么就有: |
| 74 | + |
| 75 | +- 如果最后一块选择了形状 3,那么刚好铺满 2 \* (N - 1)瓷砖,最后一列只有一块瓷砖的总的可能数是 T(n -1) |
| 76 | +- 如果最后一块选择了形状 5,那么刚好铺满 2 \* (N - 1)瓷砖,最后一列只有一块瓷砖的总的可能数是 T(n-1) |
| 77 | + |
| 78 | +> 大家可以根据基本图形画一下试试就知道了。同时你也应该理解了 4 和 6 在什么情况下使用。 |
| 79 | +
|
| 80 | +根据以上的信息有如下公式: |
| 81 | + |
| 82 | +``` |
| 83 | +F(n) = F(n-1) + F(n-2) + 2 * T(n-1) |
| 84 | +``` |
| 85 | + |
| 86 | +由于上述等式有两个变量,因此至少需要两个这样的等式才可解。而上面的等式是 F(n) = xxx,因此一个直觉找到一个类似 T(n) = xxx 的公式。不难发现如下等式: |
| 87 | + |
| 88 | +``` |
| 89 | +T(n) = F(n-2) + T(n-1) |
| 90 | +``` |
| 91 | + |
| 92 | +将上面两个公式进行合并。具体来说就是: |
| 93 | + |
| 94 | +``` |
| 95 | +F(n) = F(n-1) + F(n-2) + 2 * T(n-1) |
| 96 | +T(n) = F(n-2) + T(n-1) -> T(n-1) = F(n-3) + T(n-2) -> 2 * T(n-1) = 2 * F(n-3) + 2 * T(n-2) |
| 97 | +``` |
| 98 | + |
| 99 | +进一步: |
| 100 | + |
| 101 | +``` |
| 102 | +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) |
| 103 | +``` |
| 104 | + |
| 105 | +至此,我们得出了状态转移方程: |
| 106 | + |
| 107 | +``` |
| 108 | +F(n) = 2 * F(n-1) + F(n-3) |
| 109 | +``` |
| 110 | + |
| 111 | +## 关键点 |
| 112 | + |
| 113 | +- 识别最优子结构 |
| 114 | +- 对一块瓷砖能拼成的图形进行分解,并对每一种情况进行讨论 |
| 115 | + |
| 116 | +## 代码 |
| 117 | + |
| 118 | +- 语言支持:Python3 |
| 119 | + |
| 120 | +Python3 Code: |
| 121 | + |
| 122 | +```python |
| 123 | + |
| 124 | +class Solution: |
| 125 | + def numTilings(self, N: int) -> int: |
| 126 | + dp = [0] * (N + 3) |
| 127 | + # f(3) = 2 * f(2) + f(0) = 2 + f(0) = 1 -> f(0) = -1 |
| 128 | + # f(4) = 2 * f(3) + f(1) = 2 + f(1) = 2 -> f(1) = 0 |
| 129 | + dp[0] = -1 |
| 130 | + dp[1] = 0 |
| 131 | + dp[2] = 1 |
| 132 | + # f(n) = f(n-1) + f(n-2) + 2 * T(n-1) |
| 133 | + # 2 * T(n-1) = 2 * f(n-3) + 2 * T(n-2) |
| 134 | + # 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) |
| 135 | + for i in range(3, N + 3): |
| 136 | + dp[i] = 2 * dp[i-1] + dp[i-3] |
| 137 | + return dp[-1] % (10 ** 9 + 7) |
| 138 | + |
| 139 | +``` |
| 140 | + |
| 141 | +**复杂度分析** |
| 142 | + |
| 143 | +令 n 为数组长度。 |
| 144 | + |
| 145 | +- 时间复杂度:$O(n)$ |
| 146 | +- 空间复杂度:$O(n)$ |
| 147 | + |
| 148 | +使用滚动数组优化可以将空间复杂度降低到 $O(1)$,大家可以试试。 |
| 149 | + |
| 150 | +> 此题解由 [力扣刷题插件](https://leetcode-pp.github.io/leetcode-cheat/?tab=solution-template) 自动生成。 |
| 151 | +
|
| 152 | +力扣的小伙伴可以[关注我](https://leetcode-cn.com/u/fe-lucifer/),这样就会第一时间收到我的动态啦~ |
| 153 | + |
| 154 | +以上就是本文的全部内容了。大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 40K star 啦。大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。 |
| 155 | + |
| 156 | +关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。 |
| 157 | + |
| 158 | + |
0 commit comments