1931. 用三种不同颜色为网格涂色

题目链接

1931. 用三种不同颜色为网格涂色 - 力扣(LeetCode)

题目描述

给你两个整数 mn 。构造一个 m x n 的网格,其中每个单元格最开始是白色。请你用 红、绿、蓝 三种颜色为每个单元格涂色。所有单元格都需要被涂色。

涂色方案需要满足:不存在相邻两个单元格颜色相同的情况 。返回网格涂色的方法数。因为答案可能非常大, 返回 对 109 + 7 取余 的结果。

题目示例

示例 1 :

plain 复制代码
输入:m = 1, n = 1
输出:3
解释:如上图所示,存在三种可能的涂色方案。

示例 2 :

plain 复制代码
输入:m = 1, n = 2
输出:6
解释:如上图所示,存在六种可能的涂色方案。

解题思路

  1. 问题理解
    • 给定一个m x n的网格,用3种颜色涂色,要求相邻网格颜色不同。
    • 计算所有合法的涂色方案数,结果对10^9+7取模。
  2. 关键思路
    • 状态压缩:将每列的颜色状态用三进制数表示(0/1/2对应三种颜色)。
    • 有效状态筛选:生成所有列内相邻颜色不同的有效状态。
    • 状态转移图:构建状态之间的转移关系(相邻列对应位置颜色不同)。
    • 记忆化搜索:动态规划计算方案数,避免重复计算。
  3. 算法流程
    • 预处理3的幂次数组pow3,用于状态处理。
    • 生成所有有效的列颜色状态valid
    • 构建状态转移图nxt,表示每个状态可以转移到哪些其他状态。
    • 使用记忆化搜索dfs计算方案数,从最后一列开始递归。
    • 汇总所有可能的初始状态的方案数,返回结果。

题解代码

java 复制代码
class Solution {
    private static final int MOD = 1_000_000_007; // 模数,防止结果溢出

    public int colorTheGrid(int m, int n) {
        // 计算3的幂次数组,用于后续处理颜色状态
        int[] pow3 = new int[m];
        pow3[0] = 1;
        for (int i = 1; i < m; i++) {
            pow3[i] = pow3[i - 1] * 3;
        }

        // 生成所有有效的列颜色状态(相邻颜色不同)
        List<Integer> valid = new ArrayList<>();
        next:
        for (int color = 0; color < pow3[m - 1] * 3; color++) {
            for (int i = 1; i < m; i++) {
                // 检查相邻颜色是否相同
                if (color / pow3[i] % 3 == color / pow3[i - 1] % 3) {
                    continue next; // 相同则跳过
                }
            }
            valid.add(color); // 有效状态加入列表
        }

        // 构建状态转移图:每个有效状态可以转移到哪些其他有效状态
        int nv = valid.size();
        List<Integer>[] nxt = new ArrayList[nv];
        Arrays.setAll(nxt, i -> new ArrayList<>());
        for (int i = 0; i < nv; i++) {
            next2:
            for (int j = 0; j < nv; j++) {
                for (int p3 : pow3) {
                    // 检查对应位置颜色是否相同
                    if (valid.get(i) / p3 % 3 == valid.get(j) / p3 % 3) {
                        continue next2; // 相同则跳过
                    }
                }
                nxt[i].add(j); // 可以转移的状态
            }
        }

        // 记忆化数组,memo[i][j]表示第i列使用状态j时的方案数
        int[][] memo = new int[n][nv];
        for (int[] row : memo) {
            Arrays.fill(row, -1);
        }

        // 计算所有可能的初始状态的总方案数
        long ans = 0;
        for (int j = 0; j < nv; j++) {
            ans += dfs(n - 1, j, nxt, memo);
        }
        return (int) (ans % MOD);
    }

    // 记忆化搜索
    private int dfs(int i, int j, List<Integer>[] nxt, int[][] memo) {
        if (i == 0) {
            return 1; // 基础情况:第一列
        }
        if (memo[i][j] != -1) {
            return memo[i][j]; // 已计算过
        }
        long res = 0;
        // 遍历所有可能的下一个状态
        for (int k : nxt[j]) {
            res += dfs(i - 1, k, nxt, memo);
        }
        return memo[i][j] = (int) (res % MOD); // 存储并返回结果
    }
}

复杂度分析

  1. 时间复杂度
    • 生成有效状态:O(m * 3^m)
    • 构建状态转移图:O(m * 3^(2m))
    • 记忆化搜索:O(n * 3^m)
    • 总体:O(n * 3^m + m * 3^(2m)),当m较小时可行
  2. 空间复杂度
    • 存储状态转移图:O(3^(2m))
    • 记忆化数组:O(n * 3^m)
    • 总体:O(n * 3^m + 3^(2m))
相关推荐
列星随旋20 分钟前
线段树和树状数组的学习
学习·算法
圣保罗的大教堂37 分钟前
leetcode 61. 旋转链表 中等
leetcode
全糖可乐气泡水2 小时前
Codex适配国产信创环境安装部署与技术适配全解析
开发语言·git·python·算法·百度
h_a_o777oah2 小时前
状态机+划分型 DP :深度解析K-划分问题下 DP 状态的转移逻辑(洛谷P2679 P2331 附C++代码)
c++·算法·动态规划·acm·状态机dp·划分型dp·滚动数组优化
05候补工程师2 小时前
从算法理想向工程现实的跨越:SLAM 核心架构、思维误区与 Nav2 实战避坑指南
人工智能·算法·安全·架构·机器人
手写码匠4 小时前
Android 17 适配实战指南:新特性解读、隐私变更与迁移全攻略
人工智能·深度学习·算法·aigc
珊瑚里的鱼4 小时前
leetcode42雨水
算法·leetcode
水木流年追梦4 小时前
大模型入门-大模型的推理策略
开发语言·python·算法·正则表达式·prompt
生成论实验室4 小时前
用事件关系网络重新理解AI(三):激活函数、微调与元学习
人工智能·学习·算法·语言模型·可信计算技术
Narv工程师5 小时前
嵌入式机器人控制器算力评估:从DMIPS到WCET的完整指南
人工智能·算法·机器学习