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(三维动态规划)