LeetCode 粉刷房子合集

Posted Zephyr丶J

tags:

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

LeetCode 粉刷房子合集

2021.5.4的每日一题,粉刷房子III,困难题,看了一下,不会做,然后因为是III,下意识的去找了一下I和II,然后发现是VIP…
因为粉刷房子I 和II 普通人看不到,所以这里粘贴一下题目,方便以后查看

粉刷房子I(容易)

题目描述

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

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

例如,costs[0][0] 表示第 0 号房子粉刷成红色的成本花费;costs[1][2] 表示第 1 号房子粉刷成绿色的花费,以此类推。请你计算出粉刷完所有房子最少的花费成本。

注意:

所有花费均为正整数。

示例:

输入: [[17,2,17],[16,16,5],[14,3,19]]
输出: 10
解释: 将 0 号房子粉刷成蓝色,1 号房子粉刷成绿色,2 号房子粉刷成蓝色。
最少花费: 2 + 5 + 3 = 10。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/paint-house
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

有点不一样的动态规划,定义dp[i][j]为将第 i 间房子粉刷为颜色 j 时的最小花费。因为相邻的房子颜色不能相同,那么当前房子的最小花费就与前面房子的颜色有关,当前房子能选的颜色有三种,而每一种颜色都结果由前面房子涂其他两种颜色的结果决定
初始化即为第一间房子涂三种颜色的花费
(无法测试,代码不一定对)

public class Solution {
    public int minCost(int[][] costs) {
        int l = costs.length;
        if (l == 0) {
            return 0;
        }
        //l间房子,3种颜色
        int[][] dp = new int[l][3];
        //初始化
        Arrays.fill(dp, Integer.MAX_VALUE);
        for (int i= 0; i < 3; i++) {
            dp[0][i] = costs[0][i];
        }
        //从第二个房子开始涂
        for (int i = 1; i < l; i++) {
        	//分别涂三种颜色
            dp[i][0] = costs[i][0] + Math.min(dp[i - 1][1], dp[i - 1][2]); 
            dp[i][1] = costs[i][1] + Math.min(dp[i - 1][0], dp[i - 1][2]);
            dp[i][2] = costs[i][2] + Math.min(dp[i - 1][0], dp[i - 1][1]);
        }
        return Math.min(dp[l - 1][0], Math.min(dp[l - 1][1], dp[l - 1][2]));
    }
}

粉刷房子II(中等)

题目描述

假如有一排房子,共 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. 
进阶:
您能否在 O(nk) 的时间复杂度下解决此问题?

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/paint-house-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

相比于I,这个题目中的颜色变成了K种,那么还是动态规划,dp数组的定义也没有发生变化
转移的过程相当于是从2种,变成了k-1种,那么如何进行优化呢
因为在涂当前房子的时候,应该是从上一个房子花费最少的颜色转移过来的,但是还有个条件就是当前房子的颜色不能和上一个房子颜色相同,那么就应该记录上一个房子花费最少和次少的两种颜色,防止发生当前颜色和上一个房子颜色相同的情况
(无法测试,代码不一定对)

public class Solution {
    public int  minCostII(int[][] costs) {
        int m = costs.length;
        if (l == 0) {
            return 0;
        }
        int n = costs[0].length;
        //l间房子,n种颜色
        int min = Integer.MAX_VALUE;	//最小值
        int premin = Integer.MAX_VALUE;		//次小值
        int mincolor = -1;					//最小值对应的颜色

        //为了方便记录最小值和次小值,从第一个房子开始涂,加判断
        for (int i = 0; i < m; i++) {
        	//分别涂n种颜色,下面变量用于遍历当前房子的所有颜色
        	int curmin = Integer.MAX_VALUE;
        	int curpremin = Integer.MAX_VALUE;
        	int cost = 0;
        	int curmincolor = -1;
        	for(int j = 0; j < n; j++){
        		if(i == 0)
        			cost = costs[i][j];
       			//如果不是第一间,那么就需要转移
       			cost = j == mincolor ? costs[i][j] + premin : costs[i][j] + min;
       			if(cost < curmin){
       				curpremin = curmin;
       				curmin = cost;
       				curmincolor = j;
       			}
       			else if(cost < curpremin){
					curpremin = cost;
				}
        	}
            //当前房子的所有颜色遍历完了,把当前房子三个变量存储一下
            min = curmin;
            premin = curpremin;
            mincolor = curmincolor;
        }
        return min;
    }
}

粉刷房子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。
示例 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 个街区。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/paint-house-iii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

多了个啥呢,就是有的房子已经被涂好了,而且相邻房子如果颜色相同的话会被划分为一个街区
三维动态规划

class Solution {
    int INF = 0x3f3f3f3f;   //跟三叶姐学的,不设置为MAX_VALUE,是因为防止加一个数后溢出
    public int minCost(int[] houses, int[][] cost, int m, int n, int target) {
        //做了I,II,再看III,明白是动态规划了,然后呢,
        //定义dp[i][j][k]为第 i 个房子,粉刷为 j 颜色,并且此时分区为 k 的最小花费吗
        //那么该怎么转移呢,
        //如果当前房子被涂色了(j != 0) dp[i][j][k] = dp[i - 1][k](和前面颜色相同) or dp[i - 1][k - 1](颜色不同)
        //如果当前房子没有被涂色(j == 0),就能涂任意颜色,因为求的是最小花费,所以应该也要和II中一样遍历所有颜色
        //如果颜色不同,那么就不是一个分区 dp[i][j][k] = dp[i - 1][mincolor][k - 1] + cost[i][j]
        //如果和mincolor颜色相同,是一个分区 从dp[i - 1][mincolor][k] + cost[i][j]转移来
        //先练一遍最繁琐但是最容易理解的思路,参考三叶姐的代码

        int[][][] dp = new int[m + 1][n + 1][target + 1];
        //初始化
        for(int i = 0; i <= m; i++){
            for(int j = 0; j <= n; j++){
                //没有街区为0的情况
                dp[i][j][0] = INF;
            }
        }

        for(int i = 1; i <= m; i++){
            //当前房子的颜色
            int color = houses[i - 1];
            for(int j = 1; j <= n; j++){
                for(int k = 1; k <= target; k++){
                    if(k > i){
                        //如果街区数比房子数还多,不可能,直接break
                        dp[i][j][k] = INF;
                        break;
                    }
                    //如果当前房子已经涂色
                    if(color != 0){
                        //只有遍历到当前颜色和房子颜色相同时,才进行操作
                        if(j == color){
                            int curcost = INF;
                            //遍历前一个房子的所有颜色,选出最小的花费(与当前颜色不同)
                            for(int t = 1; t <= n; t++){
                                if(t != j)
                                    curcost = Math.min(curcost, dp[i - 1][t][k - 1]);
                            }
                            //如果与当前颜色相同
                            dp[i][j][k] = Math.min(curcost, dp[i - 1][j][k]);
                        }else{
                            //其他状态无效
                            dp[i][j][k] = INF;
                        }
                    //如果当前房子没有涂色,那么可以涂任意颜色
                    }else{
                        int temp = cost[i - 1][j - 1];
                        int curcost = INF;
                            //遍历前一个房子的所有颜色,选出最小的花费(与当前颜色不同)
                            for(int t = 1; t <= n; t++){
                                if(t != j)
                                    curcost = Math.min(curcost, dp[i - 1][t][k - 1]);
                            }
                            //如果与当前颜色相同
                            dp[i][j][k] = Math.min(curcost, dp[i - 1][j][k]) + temp;
                    }
                }
            }
        }
        //从所有房间形成target个街区的状态中找到花费最小的
        int res = INF;
        for(int i = 1; i <= n; i++){
            res = Math.min(res, dp[m][i][target]);
        }
        return res == INF ? -1 : res;
    }
}

如何进行优化呢,应该也是和上一个题一样,因为每次选的是花费最小的颜色,因此,也是要记录最小的两种颜色,但是如果沿用上面粉刷房子II的思路,用三个变量来记录前一个房子的三个特殊值,因为目前这道题中会有直接给定的房子颜色(就是已经上色的房子),而对它进行转移的时候,有一个转移过程是和前面房子颜色一样的,并不是从这三个值里面选,因此感觉会行不通
因此这里动规的时候,并没有直接把颜色这一维度从状态定义里去掉,空间复杂度并没有优化,只是将dp[i][k]的三个特殊值另外开辟空间进行存储,参考官解的代码改动上面的代码

class Solution {
    int INF = 0x3f3f3f3f;   //跟三叶姐学的,不设置为MAX_VALUE,是因为防止加一个数后溢出
    public int minCost(int[] houses, int[][] cost, int m, int n, int target) {
        //做了I,II,再看III,明白是动态规划了,然后呢,
        //定义dp[i][j][k]为第 i 个房子,粉刷为 j 颜色,并且此时分区为 k 的最小花费吗
        //那么该怎么转移呢,
        //如果当前房子被涂色了(j != 0) dp[i][j][k] = dp[i - 1][k](和前面颜色相同) or dp[i - 1][k - 1](颜色不同)
        //如果当前房子没有被涂色(j == 0),就能涂任意颜色,因为求的是最小花费,所以应该也要和II中一样遍历所有颜色
        //如果颜色不同,那么就不是一个分区 dp[i][j][k] = dp[i - 1][mincolor][k - 1] + cost[i][j]
        //如果和mincolor颜色相同,是一个分区 从dp[i - 1][mincolor][k] + cost[i][j]转移来
        //优化时间复杂度
		
        int[][][] dp = new int[m + 1][n + 1][target + 1];
        //初始化
        for(int i = 0; i <= m; i++){
            for(int j = 0; j <= n; j++){
                //没有街区为0的情况
                dp[i][j][0] = INF;
            }
        }
        //创建一个best三维数组,用于记录dp[i][k]的三个值,即最小值,次小值和最小值对应的颜色下标
        int[][][] best = new int[m + 1][target + 1][3];
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < target; ++j) {
                best[i][j][0] = best[i][j][2] = INF;
                best[i][j][1] = -1;
            }
        }

        for(int i = 1; i <= m; i++){
            //当前房子的颜色
            int color = houses[i - 1];
            for(int j = 1; j <= n; j++){
                for(int k = 1; k <= target; k++){
                    if(k > i){
                        //如果街区数比房子数还多,不可能,直接break
                        dp[i][j][k] = INF;
                        break;
                    }
                    //上一个状态的三个值
                    int min = best[i - 1][k - 1][0];
                    int min_color = best[i - 1][k - 1][1];
                    int premin = best[i - 1][k - 1][2];
                    //如果当前房子已经涂色
                    if(color != 0){
                        //只有遍历到当前颜色和房子颜色相同时,才进行操作
                        if(j == color){
                            int curcost = INF;
                            //遍历前一个房子的所有颜色,选出最小的花费(与当前颜色不同),这里直接用前面记录好的值
                            curcost = j == min_color ? premin : min;
                            //如果与当前颜色相同
                            dp[i][j][k] = Math.min(curcost, dp[i - 1][j][k]);
                        }else{
                            //其他状态无效
                            dp[i][j][k] = INF;
                        }
                    //如果当前房子没有涂色,那么可以涂任意颜色
                    }else{
                        int temp = cost[i - 1][j - 1];
                        int curcost = INF;
                            //遍历前一个房子的所有颜色,选出最小的花费(与当前颜色不同),这里直接用前面记录好的值
                        curcost = j == min_color ? premin : min;
                            //如果与当前颜色相同
                        dp[i][j][k] = Math.min(curcost, dp[i - 1][j][k]) + temp;
                    }
                    //更新当前状态
                    min = best[i][k][0];
                    min_color = best[i][k][1];
                    premin = best[i][k][2];
                    if(dp[i][j][k] < min){
                        best[i][k][2] = best[i][k][0];
                        best[i][k][1] = j;
                        best[i][k][0] = dp[i][j][k];
                    }
                    else if(dp[i][j][k] < premin){
                        best[i][k][2] = dp[i][j][k];
                    }

                }
            }
        }
        //从所有房间形成target个街区的状态中找到花费最小的

以上是关于LeetCode 粉刷房子合集的主要内容,如果未能解决你的问题,请参考以下文章

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

LeetCode 剑指Offer II 091 粉刷房子[动态规划] HERODING的LeetCode之路

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

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

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

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