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))
相关推荐
晨曦夜月2 小时前
map与unordered_map区别
算法·哈希算法
图码2 小时前
如何用多种方法判断字符串是否为回文?
开发语言·数据结构·c++·算法·阿里云·线性回归·数字雕刻
handler012 小时前
Linux 内核剖析:进程优先级、上下文切换与 O(1) 调度算法
linux·运维·c语言·开发语言·c++·笔记·算法
minglie12 小时前
实数列的常用递推模式
算法
代码小书生3 小时前
math,一个基础的 Python 库!
人工智能·python·算法
AI科技星3 小时前
全域数学·数术本源·高维代数卷(72分册)【乖乖数学】
人工智能·算法·数学建模·数据挖掘·量子计算
生成论实验室3 小时前
《事件关系阴阳博弈动力学:识势应势之道》第一篇:生成正在发生——从《即事经》到事件-关系网络
人工智能·科技·算法·架构·创业创新
漂流瓶jz3 小时前
UVA-1152 和为0的4个值 题解答案代码 算法竞赛入门经典第二版
数据结构·算法·二分查找·题解·aoapc·算法竞赛入门经典·uva
leoufung3 小时前
LeetCode 76:Minimum Window Substring 题解与滑动窗口思维详解
算法·leetcode·职场和发展