JAVA程序设计:多米诺和托米诺平铺(LeetCode:790)

有两种形状的瓷砖:一种是 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;
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值