• home > theory > algorithm > leetcode >

    DP(18):粉刷房子系列问题

    Author:zhoulujun Date:

    粉刷房子总共有三题256 粉刷房子(中等)265 粉刷房子 II(困难)1473 粉刷房子 III(困难)

    粉刷房子总共有三题

    • 256. 粉刷房子(中等) ,等同 LCR 091. 粉刷房子:限定3中颜色(n x 3 的矩阵 costs),且相邻的房子颜色不能相同。求将所有房子粉刷完的最低成本。
    • 265. 粉刷房子 II(困难):与I相似,但是颜色数量扩展到k种( n x k 的矩阵 costs)。
    • 1473. 粉刷房子 III(困难):定一个 n x k 的矩阵 costs,表示每个房子需要粉刷 k 种颜色的成本,还有一个非负整数 target 表示你有多少钱,求粉刷前 n 个房子使得恰好有 target 个房子涂成相同颜色的最低成本。


    粉刷房子

    假如有一排房子,共 n 个,每个房子可以被粉刷成红色、蓝色或者绿色这三种颜色中的一种,你需要粉刷所有的房子并且使其相邻的两个房子颜色不能相同。

    当然,因为市场上不同颜色油漆的价格不同,所以房子粉刷成不同颜色的花费成本也是不同的。每个房子粉刷成不同颜色的花费是以一个 n x 3 的正整数矩阵 costs 来表示的。

    例如,costs[0][0] 表示第 0 号房子粉刷成红色的成本花费;costs[1][2] 表示第 1 号房子粉刷成绿色的花费,以此类推。

    请计算出粉刷完所有房子最少的花费成本。

    示例 1:输入: costs = [[17,2,17],[16,16,5],[14,3,19]]      输出: 10

    解释: 将 0 号房子粉刷成蓝色,1 号房子粉刷成绿色,2 号房子粉刷成蓝色。最少花费: 2 + 5 + 3 = 10。

    剑指Offer给的答案还是看不懂:https://acoier.com/2023/11/03/剑指%20Offer%20II%20091.%20粉刷房子(中等)/

    class Solution {
        public int minCost(int[][] costs) {
            int r = 0, g = 0, b = 0;
            for (int[] cost : costs) {
                int _r = r, _g = g, _b = b;
                r = Math.min(_g, _b) + cost[0];
                g = Math.min(_r, _b) + cost[1];
                b = Math.min(_r, _g) + cost[2];
            }
            return Math.min(r, Math.min(g, b));
        }
    }

    动态规划解法:

    你要用红/蓝/绿三种不同的颜色去粉刷 n 个房子,一个房子只能刷成一种颜色,并且相邻的房子不能粉刷相同的颜色。

    粉房子-1.png

    动态分析

    找到 “状态” 和 选择:
      • 状态:初始 d(0, 0) = a(0, 0)、d(0, 1) = a(0, 1) 、d(0, 2) = a(0, 2)

      • 选择:选择粉刷费用最小的,相邻的房子不能粉刷相同的颜色。

      dp 数组定义:

       d(i,j) 表示粉刷 0~i号房子,并且第 i号房子使用第 j 种颜色的最小费用。


      • j 的取值只有 0/1/2 三种, 分别表示红/蓝/绿 3种颜色。

      状态之间的关系:
      • d(i,0) = min(d(i-1, 1), d(i-1, 2)) + a(i, 0)

      • d(i,1) = min(d(i-1, 0), d(i-1, 2)) + a(i, 1)

      • d(i,2) = min(d(i-1, 0), d(i-1, 1)) + a(i, 2)

    public class LeetCode_256 {
    
        // Time: O(n), Space: O(n)
        public int minCost(int[][] costs) {
            if (costs == null || costs.length == 0) return 0;
            int n = costs.length;
            int[][] d = new int[n][3];
            d[0][0] = costs[0][0];
            d[0][1] = costs[0][1];
            d[0][2] = costs[0][2];
    
            for (int i = 1; i < n; ++i) {
                d[i][0] = Math.min(d[i-1][1], d[i-1][2]) + costs[i][0];
                d[i][1] = Math.min(d[i-1][0], d[i-1][2]) + costs[i][1];
                d[i][2] = Math.min(d[i-1][0], d[i-1][1]) + costs[i][2];
            }
            return min(d[n-1][0], d[n-1][1], d[n-1][2]);
        }
    
        private int min(int a, int b, int c) {
            return Math.min(a, Math.min(b, c));
        }
    }


    优化一:统一初始计算到循环中

    d(i, j) 新定义: 表示粉刷 0~i-1 号房子,并且第 i-1 号房子使用第 j 种颜色的最小费用。

    public class LeetCode_256 {
    
        // Time: O(n), Space: O(n)
        public int minCostOpt(int[][] costs) {
            if (costs == null || costs.length == 0) return 0;
            int n = costs.length;
            int[][] d = new int[n+1][3];
    
            for (int i = 1; i <= n; ++i) {
                d[i][0] = Math.min(d[i-1][1], d[i-1][2]) + costs[i-1][0];
                d[i][1] = Math.min(d[i-1][0], d[i-1][2]) + costs[i-1][1];
                d[i][2] = Math.min(d[i-1][0], d[i-1][1]) + costs[i-1][2];
            }
            return min(d[n][0], d[n][1], d[n][2]);
        }
    }

    优化二:空间复杂度优化到常量

    public class LeetCode_256 {
    
        // Time: O(n), Space: O(1)
        public int minCostO1(int[][] costs) {
            if (costs == null || costs.length == 0) return 0;
            int n = costs.length;
            int preR = 0, preB = 0, preG = 0;
    
            for (int i = 1; i <= n; ++i) {
                int curR = Math.min(preB, preG) + costs[i-1][0];
                int curB = Math.min(preR, preG) + costs[i-1][1];
                int curG = Math.min(preR, preB) + costs[i-1][2];
                preR = curR;
                preB = curB;
                preG = curG;
            }
            return min(preR, preB, preG);
        }
    }




    粉刷房子 II(K 种颜色粉刷房子)

    假如有一排房子,共 n 个,每个房子可以被粉刷成 k 种颜色中的一种,你需要粉刷所有的房子并且使其相邻的两个房子颜色不能相同。

    当然,因为市场上不同颜色油漆的价格不同,所以房子粉刷成不同颜色的花费成本也是不同的。

    每个房子粉刷成不同颜色的花费是以一个 n x k 的矩阵来表示的。

    例如,costs[0][0] 表示第 0 号房子粉刷成 0 号颜色的成本花费;costs[1][2] 表示第 1 号房子粉刷成 2 号颜色的成本花费,以此类推。

    请你计算出粉刷完所有房子最少的花费成本。


    示例:输入: [[1,5,3],[2,9,4]]   输出: 5

    解释: 将 0 号房子粉刷成 0 号颜色,1 号房子粉刷成 2 号颜色。最少花费: 1 + 4 = 5;  或者将 0 号房子粉刷成 2 号颜色,1 号房子粉刷成 0 号颜色。最少花费: 3 + 2 = 5. 

    动态规划解法:

    1. 找到 “状态” 和 选择:

      • 选择:选择粉刷费用最小的,相邻的房子不能粉刷相同的颜色

    2. dp 数组定义: d(i, j) 表示粉刷 0~i-1 号房子,并且第 i-1 号房子使用第 j 种颜色的最小费用。

    3. 状态之间的关系: d(i,j) = min(d(i-1, c)) + a(i-1, j)

      • c: 0 -> k-1, c != j

      • i: 1 -> n

      • j: 0 -> k-1

      • 最后结果求: min(d(n, i)), i: 0 -> k-1

    public class LeetCode_265 {
    
        // Time: O(n*k^2), Space: O(n*k)
        public int minCost(int[][] costs) {
            if (costs == null || costs.length == 0) return 0;
            int n = costs.length, k = costs[0].length;
            if (k == 1) return costs[0][0];
            int[][] d = new int[n + 1][k];
    
            for (int i = 1; i <= n; ++i) {
                for (int j = 0; j < k; ++j) {
                    int min = Integer.MAX_VALUE;
                    for (int c = 0; c < k; ++c) {
                        if (c != j) min = Math.min(min, d[i - 1][c]);
                    }
                    d[i][j] = min + costs[i - 1][j];
                }
            }
            int min = d[n][0];
            for (int i = 1; i < k; ++i) {
                min = Math.min(min, d[n][i]);
            }
            return min;
        }
    }

    优化一:

    粉房子-2.png

    public class LeetCode_265 {
    
        // Time: O(n*k), Space: O(n*k)
        public int minCostOpt(int[][] costs) {
            if (costs == null || costs.length == 0) return 0;
            int n = costs.length, k = costs[0].length;
            if (k == 1) return costs[0][0];
            int[][] d = new int[n + 1][k];
            int preIdx1 = 0, preIdx2 = 1;
    
            for (int i = 1; i <= n; ++i) {
                int idx1 = -1, idx2 = -1;
                for (int j = 0; j < k; ++j) {
                    if (j != preIdx1) d[i][j] = d[i - 1][preIdx1] + costs[i - 1][j];
                    else d[i][j] = d[i - 1][preIdx2] + costs[i - 1][j];
    
                    if (idx1 < 0 || d[i][j] < d[i][idx1]) {
                        idx2 = idx1;
                        idx1 = j;
                    } else if (idx2 < 0 || d[i][j] < d[i][idx2]) {
                        idx2 = j;
                    }
                }
                preIdx1 = idx1;
                preIdx2 = idx2;
            }
            int min = d[n][0];
            for (int i = 1; i < k; ++i) {
                min = Math.min(min, d[n][i]);
            }
            return min;
        }
    }

    优化三:空间复杂度优化到常量

    public class LeetCode_265 {
    
        // Time: O(n*k), Space: O(1)
        public int minCostO1(int[][] costs) {
            if (costs == null || costs.length == 0) return 0;
            int n = costs.length, k = costs[0].length;
            if (k == 1) return costs[0][0];
            int preMin1 = 0, preMin2 = 0;
            int preIdx1 = 0;
    
            for (int i = 1; i <= n; ++i) {
                int min1 = Integer.MAX_VALUE, min2 = Integer.MAX_VALUE;
                int idx1 = -1;
                for (int j = 0; j < k; ++j) {
                    int cost;
                    if (j != preIdx1) cost = preMin1 + costs[i - 1][j];
                    else cost = preMin2 + costs[i - 1][j];
    
                    if (cost < min1) {
                        min2 = min1;
                        min1 = cost;
                        idx1 = j;
                    } else if (cost < min2) {
                        min2 = cost;
                    }
                }
                preMin1 = min1;
                preMin2 = min2;
                preIdx1 = idx1;
            }
            return preMin1;
        }
    }


    粉刷房子 III

    在一个小城市里,有 m 个房子排成一排,你需要给每个房子涂上 n 种颜色之一(颜色编号为 1 到 n )。有的房子去年夏天已经涂过颜色了,所以这些房子不可以被重新涂色。

    我们将连续相同颜色尽可能多的房子称为一个街区。(比方说 houses = [1,2,2,3,3,2,1,1] ,它包含 5 个街区  [{1}, {2,2}, {3,3}, {2}, {1,1}] 。)

    给你一个数组 houses ,一个 m * n 的矩阵 cost 和一个整数 target ,其中:

    houses[i]:是第 i 个房子的颜色,0 表示这个房子还没有被涂色。

    cost[i][j]:是将第 i 个房子涂成颜色 j+1 的花费。

    请你返回房子涂色方案的最小总花费,使得每个房子都被涂色后,恰好组成 target 个街区。如果没有可用的涂色方案,请返回 -1 。


    示例 1:输入:houses = [0,0,0,0,0], cost = [[1,10],[10,1],[10,1],[1,10],[5,1]], m = 5, n = 2, target = 3    输出:9

    解释:房子涂色方案为 [1,2,2,1,1]   此方案包含 target = 3 个街区,分别是 [{1}, {2,2}, {1,1}]。  涂色的总花费为 (1 + 1 + 1 + 1 + 5) = 9。

    这是一个稍微复杂的变体,需要在动态规划的基础上引入一个维度来表示相同颜色的房子个数。

    这个其实官方 https://leetcode.cn/problems/paint-house-iii/solutions/755639/fen-shua-fang-zi-iii-by-leetcode-solutio-powb/

    解析已经非常清晰了,没有必要讲了





    参考文章:

    【动态规划】粉刷房子问题 https://juejin.cn/post/7136214969720242183




    转载本站文章《DP(18):粉刷房子系列问题》,
    请注明出处:https://www.zhoulujun.cn/html/theory/algorithm/leetcode/9161.html