1411. 给 N x 3 网格图涂色的方案数

题目

你有一个 n x 3 的网格图 grid ,你需要用 红,黄,绿 三种颜色之一给每一个格子上色,且确保相邻格子颜色不同(也就是有相同水平边或者垂直边的格子颜色不同)。

给你网格图的行数 n 。

请你返回给 grid 涂色的方案数。由于答案可能会非常大,请你返回答案对 10^9 + 7 取余的结果。

示例 1:

输入:n = 1 输出:12 解释:总共有 12 种可行的方法:

示例 2:

输入:n = 2 输出:54

示例 3:

输入:n = 3 输出:246

示例 4:

输入:n = 7 输出:106494

示例 5:

输入:n = 5000 输出:30228214

提示:

  • n == grid.length
  • grid[i].length == 3
  • 1 <= n <= 5000

解题思路

这是一道经典的动态规划(Dynamic Programming)问题。

由于网格的宽度固定为 3,而高度 $n$ 很大,我们无法直接枚举所有格子的颜色。但是,我们可以一行一行地进行涂色,因为第 $i$ 行的合法涂色方案只取决于第 $i-1$ 行的颜色分布。

对于任意一行(有 3 个格子),只要满足“相邻格子颜色不同”,其颜色模式其实只有两种结构类型。我们需要关注的不是具体涂了红、黄、绿哪个颜色,而是第 1 个格子和第 3 个格子的颜色关系

我们定义两种状态类型:

  • Type 0 (ABA模式):这一行的第 1 个格子和第 3 个格子颜色相同
    • 例如:红-黄-红绿-蓝-绿
    • (中间格子肯定和两边不同,所以只要头尾相同就是这种模式)。
  • Type 1 (ABC模式):这一行的第 1 个格子和第 3 个格子颜色不同
    • 例如:红-黄-绿蓝-红-黄
    • (当然,三个格子两两之间都必须满足相邻不同)。

当 $n=1$ 时,我们只看第一行:

  • Type 0 (ABA) 的方案数
    • 第 1 格有 3 种选色。
    • 第 2 格有 2 种选色(不能和第1格一样)。
    • 第 3 格有 1 种选色(必须和第1格一样)。
    • 总数 = $3 \times 2 \times 1 = 6$ 种
  • Type 1 (ABC) 的方案数
    • 第 1 格有 3 种选色。
    • 第 2 格有 2 种选色。
    • 第 3 格有 1 种选色(不能和第2格一样,且因为是ABC模式,也不能和第1格一样)。
    • 总数 = $3 \times 2 \times 1 = 6$ 种

所以,$n=1$ 时总共有 $6+6=12$ 种,符合题目示例。

假设我们已经知道了第 $i-1$ 行属于 ABA 模式的数量是 $a_{i-1}$,属于 ABC 模式的数量是 $b_{i-1}$。我们需要计算第 $i$ 行的数量 $a_i$ 和 $b_i$。

这就需要分析不同模式之间的兼容性(即上下相邻格子颜色不能相同)。

情况 A:如果上一行是 ABA 模式(比如 1-2-1)

下一行可以是哪种模式?

  • 转变为 ABA 模式:有 3 种方法。
    • 如果上一行是 1-2-1,下一行想变成 x-y-x。经过排列组合推导,合法的 x-y-x 有 3 种(例如 2-1-2, 2-3-2, 3-1-3 等,具体推导略)。
  • 转变为 ABC 模式:有 2 种方法。
    • 如果上一行是 1-2-1,下一行变成 x-y-z 只有 2 种合法组合。

情况 B:如果上一行是 ABC 模式(比如 1-2-3)

下一行可以是哪种模式?

  • 转变为 ABA 模式:有 2 种方法。
  • 转变为 ABC 模式:有 2 种方法。

根据上面的分析,我们可以得出递推公式:

设 $a_i$ 为第 $i$ 行是 ABA模式 的方案数,$b_i$ 为第 $i$ 行是 ABC模式 的方案数。

$$\begin{cases} a_i = 3 \times a_{i-1} + 2 \times b_{i-1} \ b_i = 2 \times a_{i-1} + 2 \times b_{i-1} \end{cases}$$

结果即为 $(a_n + b_n) \pmod{10^9+7}$。

具体代码

func numOfWays(n int) int {
    const mod = 1_000_000_007
    
    aba, abc := 6, 6
    
    for range n - 1 {
        next_aba := (aba * 3 + abc * 2) % mod
        next_abc := (aba * 2 + abc * 2) % mod
        
        aba, abc = next_aba, next_abc
    }
    
    return (aba + abc) % mod
}