leetcode1473. 粉刷房子 III(超难的三维dp问题)

Posted 卷王2048

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了leetcode1473. 粉刷房子 III(超难的三维dp问题)相关的知识,希望对你有一定的参考价值。

1473. 粉刷房子 III

题目

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

我们将连续相同颜色尽可能多的房子称为一个街区。(比方说 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。

示例 2:

输入:houses = [0,2,1,2,0], cost = [[1,10],[10,1],[10,1],[1,10],[5,1]], m = 5, n = 2, target = 3
输出:11
解释:有的房子已经被涂色了,在此基础上涂色方案为 [2,2,1,2,2]
此方案包含 target = 3 个街区,分别是 [{2,2}, {1}, {2,2}]。
给第一个和最后一个房子涂色的花费为 (10 + 1) = 11。

示例 3:

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

示例 4:

输入:houses = [3,1,2,3], cost = [[1,1,1],[1,1,1],[1,1,1],[1,1,1]], m = 4, n = 3, target = 3
输出:-1
解释:房子已经被涂色并组成了 4 个街区,分别是 [{3},{1},{2},{3}] ,无法形成 target = 3 个街区。

提示:

  • m == houses.length == cost.length
  • n == cost[i].length
  • 1 <= m <= 100
  • 1 <= n <= 20
  • 1 <= target <= m
  • 0 <= houses[i] <= n
  • 1 <= cost[i][j] <= 10^4

题解

方法一



import java.util.Arrays;

public class Solution {

    /**
     * 集合:定义 f[i][j][k]为考虑前i间房子,且以第i间房子形成的所有分区数量为j,其涂的颜色为k
     * 集合划分:以第i-1个房子是那种颜色作为划分 可以
     *
     * @param houses 是第 i个房子的颜色,house[i]=0表示这个房子还没有被涂色。
     * @param cost   是将第 i 个房子涂成颜色 j+1 的花费
     * @param m      房子的数量
     * @param n      颜色的数量
     * @param target 目标街区的数量
     * @return
     */
    public int minCost(int[] houses, int[][] cost, int m, int n, int target) {
        int inf = Integer.MAX_VALUE / 2;
        int[][][] dp = new int[m][target+1][n+1];
        //因为所求集合的属性是最小值,所以先把每个房子的每种颜色的花费初始化为inf
        for (int i = 0; i < m; i++) {
            for (int j = 0; j <= target; j++) {
                Arrays.fill(dp[i][j],inf);
            }
        }

        if (houses[0] != 0) {
            dp[0][1][houses[0]] =0;
        }else {
            for(int i = 1; i <=n; i++) {
              dp[0][1][i] = cost[0][i-1];
            }
        }

        for(int i = 1; i < m; i++) {
          for(int j = 1; j <=target; j++) {
            if (houses[i]!=0){
                int k = houses[i];
                for(int u = 1; u <=n; u++) {
                    if (u == k) {
                        dp[i][j][k] = Math.min(dp[i][j][k],dp[i-1][j][u]);
                    }else {
                        dp[i][j][k] = Math.min(dp[i][j][k],dp[i-1][j-1][u]);
                    }
                }
            }else {
                for(int k = 1; k <=n; k++) {
                    for(int u = 1; u <=n; u++) {
                        if (k == u) {
                            dp[i][j][k] = Math.min(dp[i][j][k],dp[i-1][j][u]+cost[i][k-1]);
                        }else {
                            dp[i][j][k] = Math.min(dp[i][j][k],dp[i-1][j-1][u]+cost[i][k-1]);
                        }
                    }
                }
            }
          }
        }
        for(int i = 1; i <=m; i++) {
            for(int j = 1; j <=target; j++) {
                for(int k = 1; k <=n; k++) {
                    System.out.print(dp[i][j][k]+" ");
                }
                System.out.println();
            }
            System.out.println();
        }

        int res = inf;
        for(int i = 1; i <=n; i++) {
            res = Math.min(res,dp[m-1][target][i]);
        }
        return res == inf ? -1 : res;
    }
}

方法二



/**
 * @see 线性dp https://leetcode-cn.com/problems/paint-house-iii/
 */
public class Solution {
    /**
     * 集合:定义 f[i][j][k]为考虑前i间房子,且第i间房子的颜色编号为j,前i间房子形成的分区数量为k的所有方案中的「最小上色成本」
     *
     * @param houses 是第 i个房子的颜色,house[i]=0表示这个房子还没有被涂色。
     * @param cost   是将第 i 个房子涂成颜色 j+1 的花费
     * @param m      房子的数量
     * @param n      颜色的数量
     * @param target 目标街区的数量
     * @return
     */
    public int minCost(int[] houses, int[][] cost, int m, int n, int target) {
        int inf = Integer.MAX_VALUE / 2;
        int[][][] dp = new int[m + 1][n + 1][target + 1];

        //因为所求集合的属性是最小值,所以先把每个房子的每种颜色的花费初始化为inf
        for (int i = 0; i <= m; i++) {
            for (int j = 0; j <= n; j++) {
                dp[i][j][0] = inf;
            }
        }

        for (int i = 1; i <= m; i++) {//枚举第i个房子
            int color = houses[i - 1];//因为i是从1开始的,要保证颜色与原数组对应
            for (int j = 1; j <= n; j++) {//枚举当前房子可能刷的每一种颜色
                for (int neighbor = 1; neighbor <= target; neighbor++) {//枚举该方案下可以形成的每一种分区的情况
                    if (neighbor > i) {//分区数量不可能大于房子的数量
                        dp[i][j][neighbor] = inf;
                        continue;
                    }
                    if (color != 0) {//当前房子本来有颜色
                        if (j == color) {//当前房子本来的颜色是颜色j
                            int temp = inf;
                            //
                            for (int previousColor = 1; previousColor <= n; previousColor++) {//枚举前一个房子的颜色
                                if (previousColor != j) {//当前房间的颜色不等于前一个房间的颜色
                                    //对于前一个房子的前一种颜色属于前前个街区,和当前房子当前颜色形成的新的街区 的两个选择中取花钱最小的其中一个
                                    temp = Math.min(temp, dp[i - 1][previousColor][neighbor - 1]);
                                }
                            }
                            //当前房间的颜色等于上一个房间的颜色
                            dp[i][j][neighbor] = Math.min(dp[i - 1][j][neighbor], temp);
                        } else {
                            dp[i][j][neighbor] = inf;
                        }
                    } else {
                        int u = cost[i - 1][j - 1];//匹配下标
                        int temp = inf;
                        for (int previousColor = 1; previousColor <= n; previousColor++) {
                            if (previousColor != j) {
                                temp = Math.min(temp, dp[i - 1][previousColor][neighbor - 1]);
                            }
                        }
                        dp[i][j][neighbor] = Math.min(dp[i - 1][j][neighbor], temp) + u;
                    }
                }
            }
        }
        int res = inf;
        for (int i = 1; i <= n; i++) {
            res = Math.min(res, dp[m][i][target]);
        }
        return res == inf ? -1 : res;
    }

}

方法三



import java.util.Arrays;

/**
 * @see 线性dp https://leetcode-cn.com/problems/paint-house-iii/solution/fen-shua-fang-zi-iii-by-leetcode-solutio-powb/
 */
public class Solution {
        // 极大值
        // 选择 Integer.MAX_VALUE / 2 的原因是防止整数相加溢出
        static final int INFTY = Integer.MAX_VALUE / 2;

    /**
     * 集合:定义 f[i][j][k]为考虑前i间房子,且第i间房子的颜色编号为j,前i间房子形成的分区数量为k的所有方案中的「最小上色成本」
     * @param houses 是第 i个房子的颜色,house[i]=0表示这个房子还没有被涂色。
     * @param cost 是将第 i 个房子涂成颜色 j+1 的花费
     * @param m 房子的数量
     * @param n 颜色的数量
     * @param target 目标街区的数量
     * @return
     */
        public int minCost(int[] houses, int[][] cost, int m, int n, int target) {
            // 将颜色调整为从 0 开始编号,没有被涂色标记为 -1
            for (int i = 0; i < m; ++i) {
                --houses[i];
            }

            // dp 所有元素初始化为极大值
            int[][][] dp = new int[m][n][target];
            for (int i = 0; i < m; ++i) {
                for (int j = 0; j < n; ++j) {
                    Arrays.fill(dp[i][j], INFTY);
                }
            }

            for (int i = 0; i < m; ++i) {//枚举第i个房子
                for (int j = 0; j < n; ++j) {//枚举当前房子可能刷的每一种颜色
                    if (houses[i] != -1 && houses[i] != j) {//当前房子有颜色或
                        continue;
                    }

                    for (int k = 0; k < target; ++k) {
                        for (int j0 = 0; j0 < n; ++j0) {
                            if (j == j0) {
                                if (i == 0) {
                                    if (k == 0) {
                                        dp[i][j][k] = 0;
                                    }
                                } else {
                                    dp[i][j][k] = Math.min(dp[i][j][k], dp[i - 1][j][k]);
                                }
                            } else if (i > 0 && k > 0) {
                                dp[i][j][k] = Math.min(dp[i][j][k], dp[i - 1][j0][k - 1]);
                            }
                        }

                        if (dp[i][j][k] != INFTY && houses[i] == -1) {
                            dp[i][j][k] += cost[i][j];
                        }
                    }
                }
            }

            int ans = INFTY;
            for (int j = 0; j < n; ++j) {
                ans = Math.min(ans, dp[m - 1][j][target - 1]);
            }
            return ans == INFTY ? -1 : ans;
        }




}

以上是关于leetcode1473. 粉刷房子 III(超难的三维dp问题)的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode1473. 粉刷房子 III(三维动态规划)

LeetCode 粉刷房子合集

LeetCode 剑指 Offer II 091.粉刷房子 - 原地修改

Python描述 LeetCode 剑指 Offer II 091. 粉刷房子

Python描述 LeetCode 剑指 Offer II 091. 粉刷房子

Python描述 LeetCode 剑指 Offer II 091. 粉刷房子