一、题目简介
LeetCode 256《粉刷房子》(Paint House)是动态规划的经典入门题。它模拟现实中粉刷房子,每个房子有三种颜色,粉刷不能相邻同色,要求最小化总花费。此题模型广泛出现在任务分配、调度与成本优化等实际场景。
二、题目描述
有n间房子排成一排,每个房子可以粉刷红、蓝、绿三种颜色之一。粉刷每个房子的花费不同,不能有相邻两间房子粉刷成同一种颜色,求最小总花费。
输入:costs,n行3列的二维数组,costs[i][j]表示粉刷第i个房子为颜色j(0红1蓝2绿)的花费。
三、示例分析
示例1:
输入: costs = [[17,2,17],[16,16,5],[14,3,19]]
输出: 10
解释:
- 房子1涂蓝2,房子2涂绿5,房子3涂蓝3,2+5+3=10
四、解题思路与详细步骤
方案一:动态规划(自底向上/表格法)
步骤详解
- 定义
dp[i][j]为粉刷前i+1间房,第i间房涂颜色j的最小花费。 - 初始值:
dp[0][j] = costs[0][j] - 状态转移:
dp[i][j] = costs[i][j] + min(dp[i-1][k]),k≠j(即上一个房子的颜色不能和当前一样) - 答案为最后一间房三种颜色的最小值:
min(dp[n-1][0], dp[n-1][1], dp[n-1][2])
优缺点
- 直观易懂,易扩展为更多颜色。
- 空间O(n)可进一步优化到O(1)。
方案二:动态规划(滚动变量/原地修改)
步骤详解
- 原地修改
costs数组,每一行只依赖上一行三种颜色的最小值。 - 对每个房子和每种颜色,累加不能与上一个相同颜色的最小花费。
- 结束后直接取
costs[-1]三种颜色的最小值。
优缺点
- 空间O(1),无需额外数组,适合大数据。
- 可读性略低于标准dp。
五、代码实现(Python/Java/C++)
Python实现
解法一:常规DP表
def minCost(costs):
if not costs: return 0
n = len(costs)
dp = [[0]*3 for _ in range(n)]
dp[0] = costs[0][:]
for i in range(1, n):
for j in range(3):
dp[i][j] = costs[i][j] + min(dp[i-1][(j+1)%3], dp[i-1][(j+2)%3])
return min(dp[-1])
解法二:原地DP
def minCost(costs):
if not costs: return 0
for i in range(1, len(costs)):
for j in range(3):
costs[i][j] += min(costs[i-1][(j+1)%3], costs[i-1][(j+2)%3])
return min(costs[-1])
Java实现
解法一:常规DP表
class Solution {
public int minCost(int[][] costs) {
if (costs == null || costs.length == 0) return 0;
int n = costs.length;
int[][] dp = new int[n][3];
System.arraycopy(costs[0], 0, dp[0], 0, 3);
for (int i = 1; i < n; i++) {
for (int j = 0; j < 3; j++) {
dp[i][j] = costs[i][j] + Math.min(dp[i-1][(j+1)%3], dp[i-1][(j+2)%3]);
}
}
return Math.min(Math.min(dp[n-1][0], dp[n-1][1]), dp[n-1][2]);
}
}
解法二:原地DP
class Solution {
public int minCost(int[][] costs) {
if (costs == null || costs.length == 0) return 0;
for (int i = 1; i < costs.length; i++) {
for (int j = 0; j < 3; j++) {
costs[i][j] += Math.min(costs[i-1][(j+1)%3], costs[i-1][(j+2)%3]);
}
}
int n = costs.length - 1;
return Math.min(Math.min(costs[n][0], costs[n][1]), costs[n][2]);
}
}
C++实现
解法一:常规DP表
#include <vector>
#include <algorithm>
using namespace std;
class Solution {
public:
int minCost(vector<vector<int>>& costs) {
if (costs.empty()) return 0;
int n = costs.size();
vector<vector<int>> dp(n, vector<int>(3, 0));
dp[0] = costs[0];
for (int i = 1; i < n; ++i) {
for (int j = 0; j < 3; ++j) {
dp[i][j] = costs[i][j] + min(dp[i-1][(j+1)%3], dp[i-1][(j+2)%3]);
}
}
return min({dp[n-1][0], dp[n-1][1], dp[n-1][2]});
}
};
解法二:原地DP
#include <vector>
#include <algorithm>
using namespace std;
class Solution {
public:
int minCost(vector<vector<int>>& costs) {
if (costs.empty()) return 0;
int n = costs.size();
for (int i = 1; i < n; ++i) {
for (int j = 0; j < 3; ++j) {
costs[i][j] += min(costs[i-1][(j+1)%3], costs[i-1][(j+2)%3]);
}
}
return *min_element(costs[n-1].begin(), costs[n-1].end());
}
};
六、复杂度分析
- 时间复杂度:O(n),每个房子遍历三种颜色
- 空间复杂度:O(n)(解法一)或O(1)(解法二)
七、边界与细节注意
- costs为空直接返回0
- 只有一间房时直接选最小花费
- 可原地修改节省空间
- 可以类推到更多颜色和“圆环型”房子
八、模拟面试环节及答案
1. 问:原地DP为何不会影响答案?
答:
每次只用到上一行的值,且计算当前行时不会用到被覆盖的数据,因此可以安全原地修改。
2. 问:如果有k种颜色如何扩展?
答:
只需将3替换为k,遍历颜色时避开上一次相同的颜色,代码结构完全兼容。
3. 问:房子是首尾相连(圆环)时怎么办?
答:
可将问题拆成3个子问题,分别以三种颜色为起点和终点不同时,取最小值(或用环形DP)。
4. 问:如果要求输出具体的染色方案?
答:
可在DP时记录路径,或者逆推最小花费对应的染色选择,回溯方案。
5. 问:能否用贪心法?
答:
不能。贪心只考虑当前最优,无法保证全局最优(局部最小花费后续可能导致不可选),需动态规划。
九、总结升华
本题是动态规划与状态转移建模的入门代表。掌握DP与空间优化技巧后,不仅能高效解决本题,还能迁移至多颜色、多任务分配等场景,提升整体算法思维。
7万+

被折叠的 条评论
为什么被折叠?



