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 。
示例 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;
}
}