Problem
There are a row of n
houses, each house can be painted with one of the 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, and you need to cost the least. Return the minimum cost.
The cost of painting each house with a certain color is represented by a n
x 3
cost matrix. For example, costs[0][0]
is the cost of painting house 0
with color red; costs[1][2]
is the cost of painting house 1
with color green, and so on… Find the minimum cost to paint all houses.
Examples
Example 1:
Input:[[14,2,11],[11,14,5],[14,3,10]]
Output: 10
Explanation: Paint house 0 into blue, paint house 1 into green, paint house 2 into blue. Minimum cost: 2 + 5 + 3 = 10.
Example 2:
Input:[[1,2,3],[1,4,6]]
Output: 3
Solution
Method 1 - Recursion
Let’s define the color indices: 0 for red, 1 for blue, and 2 for green.
paintHouse(int i, int j)
Which returns the minimum cost to paint the first ‘i’ houses (0-th based indexing) such that the last house (i-th house) is painted with j-th colour.
The recurse relation is:
Cost(i, RED) = costs[i][RED] + min(Cost(i+1, BLUE), Cost(i+1, GREEN))
Cost(i, BLUE) = costs[i][BLUE] + min(Cost(i+1, RED), Cost(i+1, GREEN))
Cost(i, GREEN) = costs[i][GREEN] + min(Cost(i+1, RED), Cost(i+1, BLUE))
Code
Java
public class Solution {
// Color indices
private static final int RED = 0;
private static final int BLUE = 1;
private static final int GREEN = 2;
public int minCost(int[][] costs) {
if (costs == null || costs.length == 0) {
return 0;
}
// Call the recursive function for each color starting on the first house
int costRed = paintHouse(0, RED, costs);
int costBlue = paintHouse(0, BLUE, costs);
int costGreen = paintHouse(0, GREEN, costs);
// Find the minimum of the three starting colors
return Math.min(costRed, Math.min(costBlue, costGreen));
}
private int paintHouse(int houseIndex, int color, int[][] costs) {
// Base case: If there are no more houses left to paint
if (houseIndex == costs.length) {
return 0;
}
// Total cost of painting the current house with 'color'
int totalCost = costs[houseIndex][color];
// Calculate the cost of painting the next house, ensuring we do not paint
// the same color as the current house.
if (color == RED) {
totalCost += Math.min(
paintHouse(houseIndex + 1, BLUE, costs),
paintHouse(houseIndex + 1, GREEN, costs));
} else if (color == BLUE) {
totalCost += Math.min(
paintHouse(houseIndex + 1, RED, costs),
paintHouse(houseIndex + 1, GREEN, costs));
} else { // color == GREEN
totalCost += Math.min(
paintHouse(houseIndex + 1, RED, costs),
paintHouse(houseIndex + 1, BLUE, costs));
}
return totalCost;
}
}
Complexity
- ⏰ Time complexity:
O(2^n)
. We know that for house 0, we have 3 choices of color. After thathouse [1..n]
have 2 choices of color. So time complexity is O(2^n). - 🧺 Space complexity:
O(n)
Method 2 - Top Down DP
Let us observe the recursion tree for the first test case of sample test 2.
- The root nodes are the possible colors for the first house,
Cost(0, R)
,Cost(0, B)
, andCost(0, G)
. - The next level nodes represent the costs of the second house given the color of the first house. For example, if the first house is painted red (
Cost(0, R)
), the second house can be painted either blue or green (Cost(1, B)
orCost(1, G)
). - The tree continues in this manner, and each possible color decision for the current house leads to two more recursive calls for the next house, with the color options that don’t match the color selected for the current house.
The overlapping subproblems become evident when you notice that for calculating Cost(1, B)
, you need Cost(2, R)
and Cost(2, G)
, and for Cost(1, G)
, you also need Cost(2, R)
and Cost(2, G)
. Thus, subproblems like Cost(2, R)
and Cost(2, G)
are computed multiple times.
To avoid this redundancy, a typical DP solution would store the results of Cost(2, R)
and Cost(2, G)
after the first computation and then reuse these stored results, which is known as memoization. This technique significantly reduces the amount of computation needed to solve the problem, transforming it from exponential to linear time complexity.
Code
Java
class Solution {
private static final int RED = 0;
private static final int BLUE = 1;
private static final int GREEN = 2;
private Integer[][] memo; // The memoization table
public int minCost(int[][] costs) {
if (costs == null || costs.length == 0) {
return 0;
}
int n = costs.length;
memo = new Integer[n][3]; // Initialize the memo table
// Start the recursion from the first house and try each color
int costRed = paintHouse(0, RED, costs);
int costBlue = paintHouse(0, BLUE, costs);
int costGreen = paintHouse(0, GREEN, costs);
// Return the minimum cost among three starting colors
return Math.min(Math.min(costRed, costBlue), costGreen);
}
private int paintHouse(int houseIndex, int color, int[][] costs) {
if (houseIndex == costs.length) {
return 0; // Reached the end of houses
}
// Check if the result is already computed and stored in memo table
if (memo[houseIndex][color] != null) {
return memo[houseIndex][color];
}
int totalCost = costs[houseIndex][color]; // Current painting cost
// Recursive calls for next house and different colors, excluding the current color
int nextCost = Integer.MAX_VALUE;
for (int nextColor = 0; nextColor < 3; nextColor++) {
if (nextColor != color) {
nextCost = Math.min(nextCost, paintHouse(houseIndex + 1, nextColor, costs));
}
}
// Add current cost to next cost and store result in memo table
totalCost += nextCost;
memo[houseIndex][color] = totalCost;
return memo[houseIndex][color];
}
}
Method 3 - DP Bottom UP
Let dp[i][j]
be our dynamic programming matrix of N * 3
dimensions to store the minimum cost to paint first ‘i’ houses such that the i-th house is painted with j-th colour.
Algorithm
In this bottom-up approach:
- The
dp
array is used to store the minimum cost to paint up to thei
-th house, ending with each of the three colors. - We start by initializing the
dp
array with the costs of painting the first house with each of the three available colors. - The algorithm then iteratively computes the minimum cost of painting each house in sequence, updating the
dp
table with the minimum cost that accounts for the constraint of not painting the same color as the previous house. - Finally, the lowest cost of painting the last house (with any color) will be the solution to the problem—that is, the total minimum cost to paint all houses while satisfying the no-color-repeat constraint.
Code
Java
public class HousePainting {
public int minCost(int[][] costs) {
if (costs == null || costs.length == 0) {
return 0;
}
int n = costs.length;
int[][] dp = new int[n][3];
// Initialize the first row of DP table with the costs of the first house for each color
for (int color = 0; color < 3; color++) {
dp[0][color] = costs[0][color];
}
for (int i = 1; i < n; i++) {
dp[i][0] = costs[i][0] + Math.min(dp[i - 1][1], dp[i - 1][2]); // If we paint current house Red
dp[i][1] = costs[i][1] + Math.min(dp[i - 1][0], dp[i - 1][2]); // If we paint current house Blue
dp[i][2] = costs[i][2] + Math.min(dp[i - 1][0], dp[i - 1][1]); // If we paint current house Green
}
return Math.min(Math.min(dp[n - 1][0], dp[n - 1][1]), dp[n - 1][2]);
}
}
Complexity
- ⏰ Time complexity:
O(n)
- 🧺 Space complexity:
O(n)
Method 4 - Space efficient DP with costs array modification
Code
Java
Code if we change our array, but spoace complexity - O(1)
public int minCost(int[][] costs) {
if (costs == null || costs.length == 0) {
return 0;
}
int n = costs.length;
for (int i = 1; i< n; i++) {
costs[i][0] += Math.min(costs[i - 1][1], costs[i - 1][2]);
costs[i][1] += Math.min(costs[i - 1][0], costs[i - 1][2]);
costs[i][2] += Math.min(costs[i - 1][0], costs[i - 1][1]);
}
return Math.min(Math.min(costs[n - 1][0], costs[ n - 1][1]), costs[n - 1][2]);
}
Complexity
- ⏰ Time complexity:
O(n)
- 🧺 Space complexity:
O(1)