题目描述

✅ 790. 多米诺和托米诺平铺

image-20260928233826448

image-20260928233826450

题意分析

用覆盖两个格子的多米诺,以及覆盖三个格子的 L 形托米诺,完整铺满一个两行、n 列的棋盘。骨牌允许旋转,不能重叠、越界或留下空格,返回不同铺法数量对 1000000007 取模的结果。

统计的是最终覆盖形态,同一种覆盖不会因为放置先后顺序不同而重复计数。L 形骨牌会让铺设边界上下不齐,因此不能只照搬仅有多米诺时的递推式。

解法:消去残缺状态后的三项递推

核心思路

[!blue]

定义 F[i] 为完整两行 i 列棋盘的铺法数,再定义 G[i] 为同样棋盘缺少最右上角一格时的铺法数。缺少最右下角的情况由上下对称得到相同数量,所以只需保存一种指定缺角状态,但组合完整棋盘时要考虑两个方向。

对 i >= 2,先看完整棋盘的最右一列。用一块竖多米诺覆盖它,前面剩 F[i - 1];用两块横多米诺一起覆盖最后两列,前面剩 F[i - 2];用一块 L 形骨牌同时覆盖最后一列两格及前一列的一格,剩下的就是一个缺角棋盘。上下两种方向各贡献 G[i - 1],因此 F[i] = F[i - 1] + F[i - 2] + 2G[i - 1]。

再看缺右上角的棋盘,最右侧只剩一个下格。它若由横多米诺覆盖,移去这块后会留下方向相反的缺角棋盘,贡献 G[i - 1];若由 L 形骨牌连同前一列两格覆盖,前面剩完整棋盘,贡献 F[i - 2]。所以 G[i] = G[i - 1] + F[i - 2],这里的递推用于 i >= 2。

这两组状态可以继续消元。比较 F[i] 与 F[i - 1] 的完整棋盘公式,再用 G[i - 1] - G[i - 2] = F[i - 3] 代换,得到 F[i] - F[i - 1] = F[i - 1] + F[i - 3],也就是 F[i] = 2F[i - 1] + F[i - 3]。这条简化公式从 i = 3 开始使用,来自完整与缺角状态的配合,不能只凭最后一块骨牌直接猜出。

初值是 F[0] = 1,表示空棋盘有一种什么都不放的方案;F[1] = 1,只能竖放;F[2] = 2,可以两块竖放或两块横放。每轮只需要前三个旧状态,所以用 a、b、c 分别保存 F[i - 3]、F[i - 2]、F[i - 1],先算新值,再整体推进。

逐轮取模保持结果余数正确,但 2 * c + a 在取模之前可能超过 32 位范围,因此两种实现都使用 64 位中间变量。

解题步骤

  1. 小于两列时直接返回一;否则初始化三个完整棋盘状态为一、一、二。
  2. 从第三列开始,用 next = (2 * c + a) % MOD 计算当前方案数。
  3. 将旧状态按 a = b、b = c、c = next 推进。
  4. 最后返回保存 F[n] 的 c。

代码实现

class Solution {
    public int numTilings(int n) {
        if (n < 2) {
            return 1;
        }

        long a = 1;
        long b = 1;
        long c = 2;

        for (int i = 3; i <= n; i++) {
            long next = (2 * c + a) % 1_000_000_007;

            a = b;
            b = c;
            c = next;
        }

        return (int) c;
    }
}
func numTilings(n int) int {
    if n < 2 {
        return 1
    }
    a, b, c := int64(1), int64(1), int64(2)
    for i := 3; i <= n; i++ {
        next := (2*c + a) % 1_000_000_007
        a, b, c = b, c, next
    }
    return int(c)
}

复杂度分析

  • 时间复杂度:$O(n)$,每个列数只计算一次常数时间的递推。
  • 空间复杂度:$O(1)$,缺角状态已经消去,只保存三个完整状态和一个临时新值。

关键点总结

[!green]

  • L 形骨牌引入缺角边界,先补全状态才能覆盖全部合法铺法。
  • 完整棋盘中的系数二来自上下对称的两种缺角方向。
  • 简化三项递推需要配套初值,先计算新项再覆盖旧项。

易错点总结

[!yellow]

  • 只使用前一项与前两项相加,会漏掉由 L 形骨牌连接缺角边界的铺法。
  • 将缺角状态当成同时包含两种方向,再在完整状态中额外乘二,会重复计数。
  • 把空棋盘方案数设为零,会让从空前缀接出完整铺法的贡献消失。
  • 在求 next 之前覆盖 a 或 c,会把递推需要的旧状态弄混。
  • 使用 32 位中间值,等溢出后再取模无法恢复正确结果。
  • 把摆放顺序当成新方案,而非比较最终骨牌覆盖,会重复统计同一种平铺。

相似题目

题目 难度 关联与区别
70. 爬楼梯 简单 同样对方案数做滚动递推,本题必须先补全残缺边界状态,再消元得到三项公式。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/22875353
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!