题目描述

✅ 256. 粉刷房子

题意分析

每间房子有三种颜色可选,costs[i] 的三列给出第 i 间房子涂成各颜色的费用。所有房子都要粉刷,相邻房子的颜色必须不同,求最小总费用。

解法:滚动 DP

核心思路

[!blue]

当前房子能用哪些颜色,只取决于上一间房子的颜色。因此处理完一段前缀后,不必保留每一种粉刷方案,只需按最后一间的三种颜色,分别保留累计费用最小的方案。相同末尾颜色的其他更贵方案,对后面的限制完全一样,永远不会更优。

用 r、g、b 表示这三个最小累计费用,分别对应 costs 的第 0、1、2 列。它们不是上一间单独的粉刷费用,而是从第一间到上一间全部粉刷完成的总费用。

若当前房子选择第 0 列的颜色,上一间只能选择另外两种颜色,所以 nextR = costs[i][0] + min(g, b)。同理,nextG = costs[i][1] + min(r, b),nextB = costs[i][2] + min(r, g)。这既排除了相邻同色,也枚举了每种当前颜色的全部合法前驱,因此取最小值就得到当前前缀的最优费用。

每一层只依赖上一层的三个数,可以滚动保存。但三个新值都必须读取旧状态,所以要先算出 nextR、nextG、nextB,再统一覆盖 r、g、b。

第一间房子没有相邻约束,三个状态直接初始化为它的三种费用。处理完最后一间后,没有规定它必须是什么颜色,因此返回三个状态中的最小值。

解题步骤

  1. 代码对空数组返回 0;非空时,用第一间房子的三种费用初始化 r、g、b。
  2. 从第二间房子开始,分别从另外两种颜色的旧状态转移,得到三个 next 状态。
  3. 三个新状态全部算完后统一覆盖旧状态。
  4. 最后一间房子的颜色不限,返回 r、g、b 的最小值。

只有一间房子时,循环不会执行,直接从它的三种费用中取最小值即可。

代码实现

class Solution {
    public int minCost(int[][] costs) {
        if (costs == null || costs.length == 0) {
            return 0;
        }

        // 三个状态都是截至当前房子、以对应颜色结束的累计最小费用。
        int r = costs[0][0];
        int g = costs[0][1];
        int b = costs[0][2];

        for (int i = 1; i < costs.length; i++) {
            // 全部新颜色只读取上一间状态,三个新值算完后才能一起覆盖旧值。
            int nextR = costs[i][0] + Math.min(g, b);
            int nextG = costs[i][1] + Math.min(r, b);
            int nextB = costs[i][2] + Math.min(r, g);

            r = nextR;
            g = nextG;
            b = nextB;
        }

        return Math.min(r, Math.min(g, b));
    }
}
func minCost(costs [][]int) int {
    if len(costs) == 0 {
        return 0
    }

    // 三个状态都是截至当前房子、以对应颜色结束的累计最小费用。
    r, g, b := costs[0][0], costs[0][1], costs[0][2]
    for i := 1; i < len(costs); i++ {
        // 全部新颜色只读取上一间状态,三个新值算完后才能一起覆盖旧值。
        nextR := costs[i][0] + min(g, b)
        nextG := costs[i][1] + min(r, b)
        nextB := costs[i][2] + min(r, g)
        r, g, b = nextR, nextG, nextB
    }
    return min(r, min(g, b))
}

func min(a, b int) int {
    if a < b {
        return a
    }
    return b
}

复杂度分析

  • 时间复杂度:$O(n)$。每间房子只计算三次状态转移。
  • 空间复杂度:$O(1)$。只保存上一层和当前层的三个状态。

关键点总结

[!green]

  • 最后颜色相同的方案,对未来的约束相同,只需保留其中累计费用最小的一个。
  • 新颜色只能从另外两种旧颜色转移,不能只保存一个不区分颜色的最小费用。
  • 每层只依赖上一层,因此三个状态就能完成动态规划。

易错点总结

[!yellow]

  • 原地依次更新三个旧状态:后算的颜色会读到本轮新值。必须用三个临时变量保存新层。
  • 允许从相同颜色转移:会把相邻同色的非法方案计入最优值,每个新状态必须排除自己的旧颜色。
  • 循环从第 0 间开始:第一间房子的花费会被重复计算;初始化后应从下标 1 开始。
  • 固定返回某一种颜色:最后一间没有指定颜色,必须在三个收尾状态中取最小值。

相似题目

题目 难度 关联与区别
265. 粉刷房子 II 困难 把三种颜色扩展为k种,可维护前一行最小与次小值,避免逐颜色枚举所有其他颜色。
64. 最小路径和 中等 同样在分层状态中累计最小费用,本题禁止相邻房子同色,原题由网格移动限制前驱。
1473. 粉刷房子 III 困难 粉刷房子系列。III 允许预先着色,并要求指定街区数,需要在末尾颜色状态上增加街区计数。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/35005026
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!