leetcode 1473. Paint House III(粉刷房子III)

There is a row of m houses in a small city, each house must be painted with one of the n colors (labeled from 1 to n), some houses that have been painted last summer should not be painted again.

A neighborhood is a maximal group of continuous houses that are painted with the same color.

For example: houses = [1,2,2,3,3,2,1,1] contains 5 neighborhoods [{1}, {2,2}, {3,3}, {2}, {1,1}].
Given an array houses, an m x n matrix cost and an integer target where:

houses[i]: is the color of the house i, and 0 if the house is not painted yet.
cost[i][j]: is the cost of paint the house i with the color j + 1.
Return the minimum cost of painting all the remaining houses in such a way that there are exactly target neighborhoods. If it is not possible, return -1.

Example 1:

Input: houses = [0,0,0,0,0], cost = [[1,10],[10,1],[10,1],[1,10],[5,1]], m = 5, n = 2, target = 3
Output: 9
Explanation: Paint houses of this way [1,2,2,1,1]
This array contains target = 3 neighborhoods, [{1}, {2,2}, {1,1}].
Cost of paint all houses (1 + 1 + 1 + 1 + 5) = 9.
Example 2:

Input: houses = [0,2,1,2,0], cost = [[1,10],[10,1],[10,1],[1,10],[5,1]], m = 5, n = 2, target = 3
Output: 11
Explanation: Some houses are already painted, Paint the houses of this way [2,2,1,2,2]
This array contains target = 3 neighborhoods, [{2,2}, {1}, {2,2}].
Cost of paint the first and last house (10 + 1) = 11.

houses数组中的数字表示第i 个房子的颜色,一共m个房子。
把连续相同颜色的分为1组,那么houses = [1,2,2,3,3,2,1,1] 这种的就分为5组 [{1}, {2,2}, {3,3}, {2}, {1,1}].

现在要把houses分成target个组,就需要把houses刷上不同的颜色以达到目的,
cost中就有n个颜色,cost = [[1,10],[10,1],[10,1],[1,10],[5,1]]这个的意思就是给第1个房子刷颜色1需要的cost是1,刷颜色2的cost是10,
同样的,给第2个房子刷1的cost是10,刷2cost是1,cost[i][j]就是第i个房子刷j+1颜色的cost

如果第i 个house已经有颜色(house[i] != 0), 保持原来的颜色,不再刷新的颜色。是0才能刷新的颜色。

求能分为target个组需要的cost, 如果不能达到,返回-1.

思路

DP
变量较多,属于高维度DP,比较有难度。

先理一下变量,总的来说有3个变量,m个房子,n种颜色,要分为target个组。
定义dp[target+1][m+1][n+1]

首先,m和n都是>=1的,那么在它们为0时是不需要刷颜色的,cost=0
所以dp[0][0][*] = 0.

然后来看状态转移:
dp[k][i][ci] 表示 把house分为k个组,用前 i 个房子,第i个房子的颜色为ci,需要的cost

现在从dp[k-1][i-1][c_i-1]出发,假如第 i 个颜色 ci != c_i-1,那么就会形成一个新的组,现在有i个房子
于是dp[k-1][i-1][c_i-1]就转变为dp[k][i][ci]
如果ci == c_i-1, 就不会形成新的组,dp[k][i-1][c_i-1] 会变成 dp[k][i][ci]
那还要加上刷ci颜色的cost, 如果house已经有颜色了,不用刷,cost=0,
反之,house没有颜色,需要刷,则cost = cost[i][ci]
总结一下,状态转移为:
dp[k][i][ci] = min(dp[k][i][ci], dp[k - (ci != c_i-1)][i-1][c_i-1] + cost)

最后要返回的是最后一个房子在所有颜色中cost最小的那个,也就是min(dp[target][m])

可能还是比较难理解,在代码中再次给出注释

代码中要注意下标,题目中的房子,颜色都是从1开始的,而数组下标是从0开始。

    public int minCost(int[] houses, int[][] cost, int m, int n, int target) {
        int INF = 1000000007;
        int res = -1;
        //target是多少个neighbors, m是多少个房子,n是多少种颜色
        int[][][] dp = new int[target+1][m+1][n+1];
        
        for(int i = 0; i <= target; i++) {
            for(int j = 0; j <= m; j++) {
                //m,n都>=1,所以m=0,n=0处不管什么颜色cost都是0
                if(i == 0 && j == 0) continue;
                Arrays.fill(dp[i][j], INF);
            }
        }
        
        //dp[k][i][c]表示用前i个house形成k个group的cost, 第i个house的颜色是c
        //状态转移:
        //如果c_i != c_i-1, 那么dp[k-1][i-1][c_i-1]到dp[k][i][c_i]
        //如果c_i == c_i-1, 那么组数没有增加:dp[k][i-1][c_i-1]到dp[k][i][c_i]
        //如果房子已经有颜色,那本次不再刷颜色:if(houses[i]!=0) cost = 0 else cost[i][c_i]
        //因此dp[k][i][c_i] = min(dp[k-1][i-1][c_i-1], dp[k][i-1][c_i-1]) + cost
        
        for(int k = 1; k <= target; k++) {
            for(int i = k; i <= m; i++) {  //k个group至少要k个house,剪枝
                int hi = houses[i-1];  //第i个house的颜色,数组下标是0开始,所以-1
                //j表示i-1
                int hj = i > 1 ? houses[i-2] : 0; //第i-1个house的颜色,数组下标是0开始,所以-1
                int si, ei, sj, ej;  //遍历起点,终点
                //剪枝:house没有颜色时需要遍历颜色,有颜色时就固定在现在的颜色
                if(hi == 0) {
                    si = 1;
                    ei = n;
                } else {
                    si = hi;
                    ei = hi;
                }
                
                if(hj == 0) {
                    sj = 1;
                    ej = n;
                } else {
                    sj = hj;
                    ej = hj;
                }
                
                for(int ci = si; ci <= ei; ci ++) {
                    //color是从1~n,而数组下标从0开始
                    int val = (ci == hi ? 0 : cost[i-1][ci-1]);
                    for(int cj = sj; cj <= ej; cj++) {
                        int diff = ci == cj ? 0 : 1;
                        dp[k][i][ci] = Math.min(dp[k][i][ci], dp[k-diff][i-1][cj] + val);
                    }
                }
                
            }
        }
        res = Arrays.stream(dp[target][m]).min().getAsInt();
        return res == INF ? -1 : res;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

蓝羽飞鸟

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

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

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

打赏作者

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

抵扣说明:

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

余额充值