Medium
题目描述
你有两种类型的瓷砖:一种是 2 x 1 的多米诺形状,另一种是三联骨牌形状。你可以旋转这些形状。
给定一个整数 n,返回铺满 2 x n 的棋盘的方案数。由于答案可能很大,请返回对 10^9 + 7 取模的结果。
在铺设中,每个正方形都必须被瓷砖覆盖。当且仅当棋盘上有两个 4 方向相邻的单元格,其中恰好有一种铺设方法使两个正方形都被一块瓷砖占据时,两种铺设才不同。
示例 1:
输入:n = 3
输出:5
解释:五种不同的方法如上所示。
示例 2:
输入:n = 1
输出:1
提示:
1 <= n <= 1000
解题思路
这是一道经典的动态规划问题。我们需要分析在 2×n 的棋盘上放置多米诺骨牌(2×1)和三联骨牌(L形)的方案数。
状态设计思路: 我们可以按列从左到右考虑,对于每一列,可能出现以下几种状态:
- 状态0:当前列完全填满
- 状态1:当前列上方有空格,下方填满
- 状态2:当前列下方有空格,上方填满
状态转移分析:
dp[i][0]:前i列完全填满的方案数dp[i][1]:前i列中第i列上方缺失、下方填满的方案数dp[i][2]:前i列中第i列下方缺失、上方填满的方案数
转移方程:
dp[i][0] = dp[i-1][0] + dp[i-1][1] + dp[i-1][2] + dp[i-2][0]dp[i][1] = dp[i-1][0] + dp[i-1][2]dp[i][2] = dp[i-1][0] + dp[i-1][1]
优化思路:
观察发现 dp[i][1] = dp[i][2],我们可以简化为两个状态:
f[i]:完全填满前i列的方案数p[i]:部分填满第i列的方案数
最终递推关系:f[i] = f[i-1] + f[i-2] + 2*p[i-1],p[i] = p[i-1] + f[i-2]
这种方法时间复杂度O(n),空间复杂度O(1),是推荐解法。
代码实现
class Solution {
public:
int numTilings(int n) {
const int MOD = 1000000007;
if (n == 1) return 1;
if (n == 2) return 2;
long long f0 = 1, f1 = 2, p1 = 1;
for (int i = 3; i <= n; i++) {
long long f2 = (f1 + f0 + 2 * p1) % MOD;
long long p2 = (p1 + f0) % MOD;
f0 = f1;
f1 = f2;
p1 = p2;
}
return f1;
}
};
class Solution:
def numTilings(self, n: int) -> int:
MOD = 1000000007
if n == 1:
return 1
if n == 2:
return 2
f0, f1, p1 = 1, 2, 1
for i in range(3, n + 1):
f2 = (f1 + f0 + 2 * p1) % MOD
p2 = (p1 + f0) % MOD
f0, f1, p1 = f1, f2, p2
return f1
public class Solution {
public int NumTilings(int n) {
const int MOD = 1000000007;
if (n == 1) return 1;
if (n == 2) return 2;
long f0 = 1, f1 = 2, p1 = 1;
for (int i = 3; i <= n; i++) {
long f2 = (f1 + f0 + 2 * p1) % MOD;
long p2 = (p1 + f0) % MOD;
f0 = f1;
f1 = f2;
p1 = p2;
}
return (int)f1;
}
}
var numTilings = function(n) {
const MOD = 1000000007;
if (n === 1) return 1;
if (n === 2) return 2;
let dp = new Array(n + 1).fill(0);
dp[0] = 1;
dp[1] = 1;
dp[2] = 2;
for (let i = 3; i <= n; i++) {
dp[i] = (2 * dp[i-1] + dp[i-3]) % MOD;
}
return dp[n];
};
复杂度分析
| 复杂度类型 | 分析 |
|---|---|
| 时间复杂度 | O(n) - 需要遍历从3到n的所有位置 |
| 空间复杂度 | O(1) - 只使用常数个变量存储状态 |