LeetCode 每日一题 1473. 粉刷房子 III

本文介绍了一种动态规划的方法来解决粉刷房子III的问题,目标是在给定成本和街区数量限制下,找到最小的涂色总花费。通过三层循环遍历房子、颜色和街区,并根据房子是否已涂色进行不同情况的处理,计算每个状态的最小花费。最后,从所有可能的涂色方案中找出总花费最小的方案。示例展示了具体的应用场景和代码实现,包括边界条件的处理和无效状态的设定。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

方法一:动态规划

解题思路

定义状态 dp[i][j][k] 表示:第 i 间房子涂颜色编号 j 且属于第 k 个街区时,需要的最小花费。所以这里是 3 层循环,伪代码如下:

for (int i = 0; i < m; i++) { // 遍历房子
	for (int j = 0; j < n; j++) { // 遍历颜色
		for (int k = 0; k < target; k++) { // 遍历街区

对于第 i 个房子,需要考虑 第 i - 1 个房间的颜色。所以在内层循环还需要枚举一次所有颜色以便计算当前最小花费,这里用 p 表示 第 i - 1 个房间的颜色,伪代码如下:

for (int i = 0; i < m; i++) { // 遍历房子
	for (int j = 0; j < n; j++) { // 遍历颜色
		for (int k = 0; k < target; k++) { // 遍历街区
			for (int p = 0; p < n; p++) { // 枚举上一个房间的颜色以便计算当前最小花费。

下面根据不同情况,来确定 dp[i][j][k] 的值

  • hs[i] == 0 即第 i 个房间未涂色时:

    1. 当 j == p ,当前颜色 j 与枚举的颜色 p 相同时:dp[i][j][k] = min(dp[i][j][k], dp[i - 1][p][k]) + cost[i][j]
    2. 反之 j != p 时:dp[i][j][k] = min(dp[i][j][k], dp[i - 1][p][k - 1]) + cost[i][j]
  • hs[i] != 0 即第 i 个房间已经涂色时:

    1. 当 j == p ,当前颜色 j 与枚举的颜色 p 相同时:dp[i][j][k] = min(dp[i][j][k], dp[i - 1][p][k])
    2. 反之 j != p 时:dp[i][j][k] = min(dp[i][j][k], dp[i - 1][p][k - 1])

涂色与未涂色的处理方式几乎相同,可以一并处理。只需要把未涂色的花费 cost[i][j] 放到后面处理即可。

一些细节

  • 「动态规划」的题,根据不同情况角标可以选择从 0 或 1 开始,这里从 1 开始,可以减去边界判断。
  • 本题的状态 dp[i][j][k] 并不一定是有效的(比如当 k 大于 i 时,街区是不可能比房间多的)。 定义状态值 INF,表示无效状态并初始化所有的 dp[i][j][k] 都为 INF。

参考代码

// 房间数 100 * 花费 10000,大于这个数即可
static int INF = 1_000_001;
public int minCost(int[] hs, int[][] cost, int m, int n, int target) {
    int[][][] dp = new int[m + 1][n + 1][target + 1];
    for (int i = 0; i <= m; i++) {
        for (int j = 0; j <= n; j++) {
            Arrays.fill(dp[i][j], INF);
        }
    }

    for (int i = 1; i <= m; i++) {
        int color = hs[i - 1];
        for (int j = 1; j <= n; j++) {
            // 房子已经涂色并且不等于当前颜色,无意义
            if (color != 0 && color != j) {
                continue;
            }
            for (int k = 1; k <= target; k++) {
                // 分区数量大于房子数量,无意义
                if (k > i) {
                    break;
                }
                // 枚举第[i - 1]个房子的颜色
                if (i > 1) {
                    for (int p = 1; p <= n; p++) {
                        dp[i][j][k] = j == p ? Math.min(dp[i][j][k], dp[i - 1][p][k]) : Math.min(dp[i][j][k], dp[i - 1][p][k - 1]);
                    }
                } else {
                    dp[i][j][k] = 0;
                }
                // 如果房子没有涂色,需要加上对应的花费
                if (dp[i][j][k] != INF && color == 0) {
                    dp[i][j][k] += cost[i - 1][j - 1];
                }
            }
        }
    }
    // 计算结果
    int ret = INF;
    for (int j = 1; j <= n; j++) {
        ret = Math.min(ret, dp[m][j][target]);
    }
    return  ret == INF ? -1 : ret;
}

执行结果
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值