有两种形状的瓷砖:一种是 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]
插个嘴:其实若没有托米诺的话,这道题其实求一波斐波那契数列即可。
方法一:动态规划(参考官方题解)。
dp[state] 表示当前列在不同状态下铺砖方式的数量。如果 state 的第 i 位是 1,表示当前列的第 i 行铺砖;如果 state 的第 i 位是 0,表示当前列的第 i 行不铺砖。其中,dp[0] 表示当前列两行都不铺;dp[1] 表示当前列第一行不铺,第二行铺;dp[2] 表示当前列第一行铺,第二行不铺;dp[3] 表示当前列的两行都铺。其实转移细节不懂的话可以看我上边提供的官方链接,这里不再提供。
class Solution {
private int mod=1000000007;
public int numTilings(int N) {
if(N<=1) return N;
long[] dp=new long[] {1,0,0,0};
for(int i=0;i<N;i++) {
long[] ndp=new long[4];
ndp[0b00]=(dp[0b00]+dp[0b11])%mod;
ndp[0b01]=(dp[0b00]+dp[0b10])%mod;
ndp[0b10]=(dp[0b00]+dp[0b01])%mod;
ndp[0b11]=(dp[0b00]+dp[0b01]+dp[0b10])%mod;
dp=ndp;
}
return (int)dp[0];
}
}
方法二:矩阵快速幂。
我们知道在一些递推的问题中,我们完全可以拿矩阵乘法进行加速求解,本题也是一样,我们可以将方法一中的每一种状态看做其他几种状态的线性组合,因此我们在每次计算下一列四种状态的铺砖方式数量时,可以将这四种线性组合看做矩阵转换。那么这个矩阵的 nn 次幂可以看作是转换了 nn 次,因此我们可以把这个问题简化为矩阵求幂的问题。复杂度可以降到对数级别。
class Solution {
private int mod=1000000007;
public int numTilings(int N) {
int[][] mat=new int[][] {{1,0,0,1},{1,0,1,0},{1,1,0,0},{1,1,1,0}};
return pow(mat,N)[0][0];
}
private int[][] pow(int[][] mat,int N){
int[][] ans=new int[4][4];
for(int i=0;i<4;i++) ans[i][i]=1;
if(N==0) return ans;
if(N==1) return mat;
if(N%2==1) return mul(pow(mat,N-1),mat);
int[][] B=pow(mat,N/2);
return mul(B,B);
}
private int[][] mul(int[][] A,int[][] B){
int[][] ans=new int[4][4];
for(int i=0;i<4;i++)
for(int j=0;j<4;j++) {
long sum=0;
for(int k=0;k<4;k++)
sum+=(long)A[i][k]*B[k][j]%mod;
ans[i][j]=(int)(sum%mod);
}
return ans;
}
}