剑指 Offer II 091. 粉刷房子
假如有一排房子,共 n 个,每个房子可以被粉刷成红色、蓝色或者绿色这三种颜色中的一种,你需要粉刷所有的房子并且使其相邻的两个房子颜色不能相同。
当然,因为市场上不同颜色油漆的价格不同,所以房子粉刷成不同颜色的花费成本也是不同的。每个房子粉刷成不同颜色的花费是以一个 n x 3 的正整数矩阵 costs 来表示的。
例如,costs[0][0] 表示第 0 号房子粉刷成红色的成本花费;costs[1][2] 表示第 1 号房子粉刷成绿色的花费,以此类推。
请计算出粉刷完所有房子最少的花费成本。
示例 1:
输入: costs = [[17,2,17],[16,16,5],[14,3,19]]
输出: 10
解释: 将 0 号房子粉刷成蓝色,1 号房子粉刷成绿色,2 号房子粉刷成蓝色。
最少花费: 2 + 5 + 3 = 10。
示例 2:
输入: costs = [[7,6,2]]
输出: 2
题解
动态规划
每个房子可以被粉刷成三种颜色中的一种,需要计算在满足相邻房子的颜色不同的情况下粉刷所有房子的最小花费成本。由于当已知粉刷前 ii 个房子的最小花费成本时,根据粉刷第
i
+
1
i+1
i+1 号房子的花费成本可以计算粉刷前
i
+
1
i+1
i+1个房子的最小花费成本,因此可以使用动态规划计算最小花费成本。
由于每个房子可以被粉刷成三种颜色中的一种,因此需要分别考虑粉刷成三种颜色时的最小花费成本。
用 dp [ i ] [ j ] \textit{dp}[i][j] dp[i][j] 表示粉刷第 0 号房子到第 i 号房子且第 i 号房子被粉刷成第 j 种颜色时的最小花费成本。由于一共有 n 个房子和 3 种颜色,因此 0 ≤ i < n 0≤i<n 0≤i<n, 0 ≤ j < 3 0≤j<3 0≤j<3。
当只有第 0 号房子被粉刷时,对于每一种颜色,总花费成本即为将第 0 号房子粉刷成该颜色的花费成本,因此边界条件是:对于任意 0 ≤ j < 3 , dp [ 0 ] [ j ] = costs [ 0 ] [ j ] 0≤j<3,\textit{dp}[0][j] = \textit{costs}[0][j] 0≤j<3,dp[0][j]=costs[0][j]。
对于 1 ≤ i < n 1≤i<n 1≤i<n,第 i 号房子和第 i−1 号房子的颜色必须不同,因此当第 i 号房子被粉刷成某一种颜色时,第 i−1 号房子只能被粉刷成另外两种颜色之一。当第 i 号房子分别被粉刷成三种颜色时,粉刷第 0 号房子到第 i 号房子的最小花费成本计算如下:
dp
[
i
]
[
0
]
=
min
(
dp
[
i
−
1
]
[
1
]
,
dp
[
i
−
1
]
[
2
]
)
+
costs
[
i
]
[
0
]
dp
[
i
]
[
1
]
=
min
(
dp
[
i
−
1
]
[
0
]
,
dp
[
i
−
1
]
[
2
]
)
+
costs
[
i
]
[
1
]
dp
[
i
]
[
2
]
=
min
(
dp
[
i
−
1
]
[
0
]
,
dp
[
i
−
1
]
[
1
]
)
+
costs
[
i
]
[
2
]
\begin{aligned} \textit{dp}[i][0] &= \min(\textit{dp}[i - 1][1], \textit{dp}[i - 1][2]) + \textit{costs}[i][0] \\ \textit{dp}[i][1] &= \min(\textit{dp}[i - 1][0], \textit{dp}[i - 1][2]) + \textit{costs}[i][1] \\ \textit{dp}[i][2] &= \min(\textit{dp}[i - 1][0], \textit{dp}[i - 1][1]) + \textit{costs}[i][2] \end{aligned}
dp[i][0]dp[i][1]dp[i][2]=min(dp[i−1][1],dp[i−1][2])+costs[i][0]=min(dp[i−1][0],dp[i−1][2])+costs[i][1]=min(dp[i−1][0],dp[i−1][1])+costs[i][2]
三种颜色的情况可以合并为一个状态转移方程,对于 1 ≤ i < n 1≤i<n 1≤i<n 和 0 ≤ j < 3 0≤j<3 0≤j<3,状态转移方程如下:
dp
[
i
]
[
j
]
=
min
(
dp
[
i
−
1
]
[
(
j
+
1
)
m
o
d
3
]
,
\textit{dp}[i][j] = \min(\textit{dp}[i - 1][(j + 1) \bmod 3],
dp[i][j]=min(dp[i−1][(j+1)mod3],
dp
[
i
−
1
]
[
(
j
+
2
)
m
o
d
3
]
)
+
costs
[
i
]
[
j
]
\textit{dp}[i - 1][(j + 2) \bmod 3]) + \textit{costs}[i][j]
dp[i−1][(j+2)mod3])+costs[i][j]
计算结束时, dp [ n − 1 ] \textit{dp}[n - 1] dp[n−1] 中的最小值即为粉刷所有房子的最小花费成本。
当 i ≥ 1 i≥1 i≥1 时,由于 dp [ i ] \textit{dp}[i] dp[i]的计算只和 dp [ i − 1 ] \textit{dp}[i - 1] dp[i−1] 有关,因此可以使用滚动数组优化空间,将空间复杂度降低到 O ( 1 ) O(1) O(1)。
代码
class Solution:
def minCost(self, costs: List[List[int]]) -> int:
dp = costs[0]
for i in range(1, len(costs)):
dp = [min(dp[j - 1], dp[j - 2]) + c for j, c in enumerate(costs[i])]
return min(dp)