There is a row of n houses, where each house can be painted one of three colors: red, blue, or green. The cost of painting each house with a certain color is different. You have to paint all the houses such that no two adjacent houses have the same color.
The cost of painting each house with a certain color is represented by an n x 3 cost matrix costs.
For example, costs[0][0] is the cost of painting house 0 with the color red; costs[1][2] is the cost of painting house 1 with color green, and so on...
Return the minimum cost to paint all houses.
Example 1:
Input: costs = [[17,2,17],[16,16,5],[14,3,19]]
Output: 10Explanation: Paint house 0 into blue, paint house 1 into green, paint house 2 into blue.
Minimum cost: 2 + 5 + 3 = 10.
Example 2:
Input: costs = [[7,6,2]]
Output: 2Example 3:
Input: costs = [[15,10,16],[10,1,11]]
Output: 16Constraints:
1 <= costs.length <= 100costs[i].length == 31 <= costs[i][j] <= 20Before attempting this problem, you should be comfortable with:
We need to paint each house with one of three colors such that no two adjacent houses have the same color. The most natural way to approach this is to consider each house in order and try all valid color choices.
For each house, we pick a color different from the previous house, add its cost, and recurse to the next house. By exploring all possible valid combinations, we can find the minimum total cost. This brute force approach considers every valid painting configuration.
dfs(i, prevColor) where i is the current house index and prevColor is the color used on the previous house.i equals the number of houses, return 0 (no more houses to paint).0, 1, 2), if the color is different from prevColor, recursively compute the cost of painting the remaining houses.0 with prevColor = -1 (indicating no previous color constraint).class Solution:
def minCost(self, costs: List[List[int]]) -> int:
n = len(costs)
def dfs(i, prevColor):
if i == n:
return 0
res = float("inf")
for c in range(3):
if c == prevColor:
continue
res = min(res, costs[i][c] + dfs(i + 1, c))
return res
return dfs(0, -1)The recursive solution recomputes the same subproblems many times. For example, computing the minimum cost to paint houses 2 through n starting with color 0 might be calculated multiple times from different paths.
We can use memoization to store results for each unique state (house index, previous color). When we encounter the same state again, we simply return the cached result instead of recomputing it.
dp where dp[i][c] stores the minimum cost to paint houses from index i to the end, given that the previous house was painted with color c.1 to handle the initial case where prevColor = -1.class Solution:
def min_cost(self, costs: List[List[int]]) -> int:
n = len(costs)
dp = [[-1] * 4 for _ in range(n)]
def dfs(i, prevColor):
if i == n:
return 0
if dp[i][prevColor + 1] != -1:
return dp[i][prevColor + 1]
res = float("inf")
for c in range(3):
if c == prevColor:
continue
res = min(res, costs[i][c] + dfs(i + 1, c))
dp[i][prevColor + 1] = res
return res
return dfs(0, -1)Instead of working top-down with recursion, we can build up the solution iteratively. For each house, we compute the minimum cost to paint it with each color, considering that we must have painted the previous house with a different color.
The key insight is that the minimum cost to paint house i with color c equals costs[i][c] plus the minimum of the costs from the previous house painted with the other two colors.
dp[i][c] represents the minimum cost to paint houses 0 through i with house i painted color c.i, compute dp[i][c] = costs[i][c] + min(dp[i-1][(c+1)%3], dp[i-1][(c+2)%3]). This adds the current cost plus the minimum from the two other colors in the previous row.class Solution:
def min_cost(self, costs: List[List[int]]) -> int:
n = len(costs)
if n == 0:
return 0
dp = [[0] * 3 for _ in range(n)]
for c in range(3):
dp[0][c] = costs[0][c]
for i in range(1, n):
for c in range(3):
dp[i][c] = (
costs[i][c] +
min(dp[i - 1][(c + 1) % 3], dp[i - 1][(c + 2) % 3])
)
return min(dp[n - 1])Looking at the bottom-up solution, we notice that computing the DP values for house i only requires the values from house i-1. We do not need to keep track of all previous houses.
This means we can reduce space from O(n) to O(1) by only storing the costs for the previous house. We maintain three variables representing the minimum costs ending with each color and update them as we process each house.
dp0, dp1, dp2 to 0, representing the minimum cost to reach the current position ending with each color.A common mistake is to simply pick the minimum cost color for each house independently. This greedy approach ignores the constraint that adjacent houses cannot have the same color. The solution must ensure that each house is painted with a different color than the previous house.
When using the space-optimized approach, updating dp0, dp1, and dp2 sequentially causes bugs because you overwrite values that are still needed for subsequent calculations. All three new values must be computed using the old values before any of them are updated. Use temporary variables or compute all new values simultaneously.
When using memoization with prevColor = -1 to indicate no previous constraint, forgetting to offset the index by 1 when accessing the cache leads to array index out of bounds errors. The cache needs to handle prevColor values from -1 to 2, requiring an offset or an extra dimension.