1473. 粉刷房子 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 。

点击查看原题

二、思路

       由于第i个房子粉刷成什么颜色,依赖于之前的房子刷的颜色进行计算,并且只依赖于前一个房子。明显是一个动态规划问题,即把之前粉刷的状态记录下来。
       根据粉刷的颜色来判断街区,现在研究一下0~i房子颜色和0~i+1房子颜色对街区划分的影响,如下:

如果i+1个房子和i房子颜色相同,则街区划分不变
如果颜色不同,则街区数量+1

       由上述推断可以得知,只需要关注前一块的颜色和街区数量,记录下该颜色和街区数量下花费的最小值即可。
       定义动态规划数组dp[i][j][k],其中i是指第i-1个房子,j是房子粉刷的第j-1种颜色,k-1指该房子颜色下的街区数量,值的含义是粉刷花费的最小值。可以得到下述的状态转移方程:(dp全部初始化为Integer.MAX_VALUE/2)。

1)当house[i] != 0时,已经有原来颜色,0 <= loc < cost[0].length
d p [ i ] [ h o u s e [ i ] − 1 ] [ k ] = { m i n ( d p [ i ] [ h o u s e [ i ] − 1 ] [ k ] , d p [ i − 1 ] [ l o c ] [ k ] ) , i f    l o c = h o u s e [ i ] − 1 m i n ( d p [ i ] [ h o u s e [ i ] − 1 ] [ k ] , d p [ i − 1 ] [ l o c ] [ k − 1 ] ) , e l s e dp[i][house[i] - 1][k]= \left\{ \begin{aligned} min(dp[i][house[i] - 1][k],dp[i-1][loc][k])&,& if~~loc=house[i] - 1\\ min(dp[i][house[i] - 1][k],dp[i-1][loc][k-1]) &,&else \\ \end{aligned} \right. dp[i][house[i]1][k]={min(dp[i][house[i]1][k],dp[i1][loc][k])min(dp[i][house[i]1][k],dp[i1][loc][k1]),,if  loc=house[i]1else
2)当house[i] == 0时,无原来颜色,0 <= loc,j < cost[0].length
d p [ i ] [ j ] [ k ] = { m i n ( d p [ i ] [ j ] [ k ] , d p [ i − 1 ] [ l o c ] [ k ] ) , i f    l o c = j m i n ( d p [ i ] [ j ] [ k ] , d p [ i − 1 ] [ l o c ] [ k − 1 ] ) , e l s e dp[i][j][k]= \left\{ \begin{aligned} min(dp[i][j][k],dp[i-1][loc][k])&,& if~~loc=j\\ min(dp[i][j][k],dp[i-1][loc][k-1]) &,&else \\ \end{aligned} \right. dp[i][j][k]={min(dp[i][j][k],dp[i1][loc][k])min(dp[i][j][k],dp[i1][loc][k1]),,if  loc=jelse

       第1)个是因为已经有原来颜色,不可以更改,所以其他颜色地方就都为Integer.MAX_VALUE/2,只有dp[i][house[i] - 1][k]可以更改。而2)是没有颜色,所有颜色都可以刷。

三、代码

class Solution {
    public int minCost(int[] houses, int[][] cost, int m, int n, int target) {
        // i第(几个-1)房子,j第(几个-1)颜色,k组成(几个-1)街区
        int[][][] dp = new int[houses.length][cost[0].length][target];
        for (int i = 0; i < dp.length; i++) {
            for (int j = 0; j < dp[0].length; j++) {
                Arrays.fill(dp[i][j], Integer.MAX_VALUE / 2);
            }
        }
        if (houses[0] == 0) {	// 第一个房子无颜色
            for (int i = 0; i < cost[0].length; i++) {  // 初始化第一行
                dp[0][i][0] = cost[0][i];	// 由于只有一个房子,所以,数量也只能为1,也就是k=0
            }
        } else {	// 有颜色则无法更改,代价也应该为0
            dp[0][houses[0] - 1][0] = 0;
        }
        for (int i = 1; i < houses.length; i++) {
            for (int j = 0; j < cost[0].length; j++) {
                // 当房子原来有颜色,那么不可改变颜色,只能更新原有颜色与之前颜色的代价,其他颜色跳过
                if (houses[i] != 0 && houses[i] != j+1) {
                    continue;
                }
                for (int k = 0; k < target; k++) {
                    for (int loc = 0; loc < cost[0].length; loc++) {
                        if (loc == j) {
                            // 与前一个房子颜色相同,分块数量一样
                            dp[i][j][k] = Math.min(dp[i][j][k], dp[i-1][loc][k]);
                        } else if (k > 0) {
                            // 与前一个颜色不同,分数数量增加了1
                            dp[i][j][k] = Math.min(dp[i][j][k], dp[i-1][loc][k-1]);
                        }
                    }
                    // 只有dp[i][j][k]有解且房子无原有颜色,才增加粉刷成本
                    if (dp[i][j][k] != Integer.MAX_VALUE/2 && houses[i] == 0) {
                        dp[i][j][k] += cost[i][j];
                    }
                }
            }
        }
        int ans = Integer.MAX_VALUE;
        // 遍历,找到分块为target的最低代价
        for (int j = 0; j < cost[0].length; j++) {
            ans = Math.min(ans, dp[houses.length - 1][j][target - 1]);
        }
        return ans == Integer.MAX_VALUE / 2 ? -1 : ans;
    }
}

       时间复杂度 O ( h o u s e s . l e n g t h ∗ ( c o s t [ 0 ] . l e n g t h ) 2 ∗ t a r g e t ) O(houses.length*(cost[0].length)^2*target) O(houses.length(cost[0].length)2target),空间复杂度 O ( h o u s e s . l e n g t h ∗ c o s t [ 0 ] . l e n g t h ∗ t a r g e t ) O(houses.length*cost[0].length*target) O(houses.lengthcost[0].lengthtarget)
       由于当前解只依赖于上一个解,所以空间复杂度可以优化到 O ( c o s t [ 0 ] . l e n g t h ∗ t a r g e t ) O(cost[0].length*target) O(cost[0].lengthtarget),这里不做优化处理。
       由于嵌套循环种的第四个循环,只找最小值即可,可以使用记忆化来记录,降低时间到 O ( h o u s e s . l e n g t h ∗ c o s t [ 0 ] . l e n g t h ∗ t a r g e t ) O(houses.length*cost[0].length*target) O(houses.lengthcost[0].lengthtarget)。这里也不做优化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

佳鑫大大

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值