leetcode
leetcode 201 ~ 250
粉刷房子

粉刷房子

难度:

标签:

题目描述

代码结果

运行时间: 25 ms, 内存: 16.1 MB


/*
 * Leetcode题目: 粉刷房子
 * 题目思路: 给定一个n x 3的数组costs,表示粉刷每一栋房子的成本。
 * 每一行表示粉刷第i栋房子的成本,costs[i][0]是粉刷成红色的成本,
 * costs[i][1]是粉刷成蓝色的成本,costs[i][2]是粉刷成绿色的成本。
 * 每一栋房子必须粉刷成一种颜色,并且不能有相邻的两栋房子粉刷成相同的颜色。
 * 求粉刷所有房子的最小成本。
 */
import java.util.Arrays;
import java.util.stream.IntStream;
 
public class PaintHouseStream {
    public int minCost(int[][] costs) {
        if (costs == null || costs.length == 0) return 0;
        int n = costs.length;
        int[][] dp = new int[n][3];
        dp[0] = Arrays.copyOf(costs[0], 3);
        IntStream.range(1, n).forEach(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 IntStream.of(dp[n-1]).min().getAsInt();
    }
}

解释

方法:

这个题解使用动态规划的方法来求解最小花费。定义 dp[i][j] 表示粉刷前 i 个房子,且第 i 个房子使用颜色 j 的最小花费。状态转移方程为:dp[i][j] = min(dp[i-1][k]) + costs[i][j],其中 k != j。边界条件为 dp[0][j] = costs[0][j]。最终答案为 min(dp[n-1][0], dp[n-1][1], dp[n-1][2]),即粉刷完所有房子时的最小花费。为了优化空间复杂度,可以只使用一维数组来存储状态,即 dp[j] 表示粉刷当前房子使用颜色 j 的最小花费。

时间复杂度:

O(n)

空间复杂度:

O(1)

代码细节讲解

🦆
在动态规划中,你是如何考虑初始化`dp`数组的边界情况?请详细解释。
在动态规划的粉刷房子问题中,初始化`dp`数组的边界情况对于正确地启动算法非常关键。边界情况指的是粉刷第一个房子的最小花费。由于第一个房子没有前一个房子,所以它的花费直接等于各个颜色的成本。具体而言,`dp[0]`、`dp[1]`和`dp[2]`分别初始化为第一个房子使用红色、蓝色和绿色的成本,即`costs[0][0]`、`costs[0][1]`和`costs[0][2]`。这样初始化确保了从第二个房子开始的状态转移能够基于这个精确的基础进行计算。
🦆
题解中提到的状态转移方程`dp[i][j] = min(dp[i-1][k]) + costs[i][j]`中,k != j的约束是如何在代码中实现的?具体的实现逻辑是什么?
在题解中,状态转移方程`dp[i][j] = min(dp[i-1][k]) + costs[i][j]`中的约束`k != j`是通过在计算每个颜色的最小花费时排除当前颜色实现的。具体来说,当计算新的花费`newDp[0]`(粉刷当前房子为红色的花费)时,代码中使用`min(dp[1], dp[2])`来确保不考虑上一个房子也是红色的情况,这是因为`dp[1]`和`dp[2]`分别代表上一个房子粉刷为蓝色和绿色的最小花费。同理,计算`newDp[1]`和`newDp[2]`时,也分别排除了当前颜色,确保满足`k != j`的条件。
🦆
为什么在更新`dp`数组时,选择使用一个新的数组`newDp`来存储更新的值,而不是直接在原有的`dp`数组上进行修改?
在更新`dp`数组时使用一个新数组`newDp`是为了保证在整个更新过程中,每次计算都使用的是上一轮的数据,而不是被当前轮次的更新影响的数据。如果直接在原有的`dp`数组上修改,那么计算`dp[j]`时可能会使用到已经被更新的`dp[k]`值,这会导致计算错误,因为状态转移依赖于前一状态的所有颜色的最小值。使用`newDp`数组确保在整个循环中,每个颜色的最小花费都是基于相同时间点的状态,从而保证了计算的准确性和动态规划的正确实现。

相关问题

打家劫舍

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

 

示例 1:

输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
     偷窃到的最高金额 = 1 + 3 = 4 。

示例 2:

输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
     偷窃到的最高金额 = 2 + 9 + 1 = 12 。

 

提示:

  • 1 <= nums.length <= 100
  • 0 <= nums[i] <= 400

打家劫舍 II

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警

给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。

 

示例 1:

输入:nums = [2,3,2]
输出:3
解释:你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。

示例 2:

输入:nums = [1,2,3,1]
输出:4
解释:你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
     偷窃到的最高金额 = 1 + 3 = 4 。

示例 3:

输入:nums = [1,2,3]
输出:3

 

提示:

  • 1 <= nums.length <= 100
  • 0 <= nums[i] <= 1000

粉刷房子 II

粉刷房子 II

栅栏涂色

栅栏涂色