Dynamic Programming
Paint House II
hard
DESCRIPTION (inspired by Leetcode.com)
You are a renowned painter who is given a task to paint n houses in a row. This time, you have k colors available. The cost of painting each house with each color is different and given in a 2D array costs:
- costs[i][j] = cost of painting house i with color j
No two neighboring houses can have the same color. Return the minimum cost to paint all houses.
Constraints:
- 1 ≤ n ≤ 100
- 1 ≤ k ≤ 20
- costs[i].length == k
- 1 ≤ costs[i][j] ≤ 1000
Follow-up: Can you solve this in O(n × k) time instead of the naive O(n × k²)?
Example 1
Input:
costs = [[4, 2, 8], [7, 1, 5], [3, 9, 6]]
Output:
8
Explanation:
- House 0: Color 0 (cost = 4)
- House 1: Color 1 (cost = 1)
- House 2: Color 0 (cost = 3)
- Total = 4 + 1 + 3 = 8
Example 2
Input:
costs = [[8, 3, 12, 5], [15, 9, 4, 7]]
Output:
7
Explanation: Paint house 0 with color 1 (cost = 3), house 1 with color 2 (cost = 4). Total = 7.
Code Editor
Python
Run your code to see results here
Have suggestions or found something wrong?
Explanation
Building Intuition
This problem is a direct extension of Paint House. If you haven't solved that one yet, start there first since this builds on those concepts.
The setup is the same: paint n houses in a row where no two adjacent houses can share a color. But, instead of just 3 colors, we now have k colors to choose from.
At first glance, you might think: "I already know how to solve Paint House, I'll just extend that solution." And you'd be right to start there. But there's a catch that makes this problem more interesting.
Approach 1: Direct Extension of Paint House
In Paint House with 3 colors, we defined dp[i][j] as the minimum cost to paint houses 0 through i, where house i uses color j. The recurrence was straightforward:
dp[i][j] = costs[i][j] + min(dp[i-1][m] for all m != j)With 3 colors, finding "the minimum of all colors except j" meant checking just 2 values. That's O(1) per cell, giving us O(n × 3) = O(n) total time.
But what happens when we scale to k colors?
For each of the k colors at each house, we need to find the minimum among k-1 previous values. That's O(k) work per color, times k colors, times n houses: O(n × k²).
The Problem is that O(n × k²) is too slow. When k = 1000, we're doing ~1 billion operations per house!
The question becomes: can we find "minimum of all except j" faster than scanning all k values every time?
The Key Insight
Let's think about what "minimum of all colors except j" actually means. Suppose we have these values from the previous row:
Now, think "what's the minimum excluding index j?", there are really only two scenarios:
Scenario 1: If j is NOT the index of the smallest value (j ≠ 0 in this case), then the minimum excluding j is just... the smallest value! We're excluding something that wasn't the minimum anyway.
Scenario 2: If j IS the index of the smallest value (j = 0 here), then we can't use that value. The next best option? The second smallest value.
Instead of scanning all k values every time, we precompute the two smallest values from the previous row. Then answering "minimum excluding j" becomes O(1)
Approach 2: Optimized DP with Min Tracking
Here's the optimized algorithm:
-
Initialize with the first row's costs. Find min1, min2, and min1Idx from this row.
-
For each subsequent house, compute new values:
- For color j: new[j] = costs[i][j] + (j == min1Idx ? min2 : min1)
- Track new min1, min2, min1Idx as we go
-
Return the minimum value in the final row.
The recurrence in code form:
minCost(i, j)
if j == min1Idx
return costs[i][j] + min2
else
return costs[i][j] + min1This gives us O(n × k) time because we do O(k) work per house (computing k new values and finding the new min1/min2), and we have n houses.
Walkthrough
Let's trace through a concrete example to see how this works in practice. We'll use costs = [[1, 5, 3], [2, 9, 4], [8, 1, 6], [4, 2, 3]] with 4 houses and 3 colors.
Step 1: Initialize with House 0
For the first house, we simply copy the costs. There's no previous house to consider.
We scan through and find: min1 = 1 (at index 0), min2 = 3. These values will determine what each color at house 1 adds to its cost.
Step 2: Process House 1
Now the optimization kicks in. For each color j at house 1:
- If j = 0 (the min1 index), we must use min2 = 3
- Otherwise, we use min1 = 1
Notice color 0's calculation: we can't use min1 (since min1 came from color 0 in the previous row), so we fall back to min2.
Step 3: Process House 2
Step 4: Process House 3 (Final)
The minimum cost to paint all 4 houses is 9, achieved by painting them: Color 0 → Color 2 → Color 1 → Color 2.
The min1/min2 tracking pattern shows up frequently! Whenever you need "minimum excluding current index" efficiently, this approach reduces O(k) lookup to O(1).
Solution
The visualization below shows the optimized algorithm in action. Watch how we track min1, min2, and min1Idx after each row, and how those values determine the additions for the next row.
Try these examples:
Visualization
Python
def min_cost_ii(costs):if not costs:return 0n, k = len(costs), len(costs[0])prev = costs[0][:]for i in range(1, n):min1, min2, min1_idx = float('inf'), float('inf'), -1for j in range(k):if prev[j] < min1:min2, min1, min1_idx = min1, prev[j], jelif prev[j] < min2:min2 = prev[j]curr = [0] * kfor j in range(k):if j == min1_idx:curr[j] = costs[i][j] + min2else:curr[j] = costs[i][j] + min1prev = currreturn min(prev)
Paint House II: minimize cost with k colors
0 / 20
What is the time complexity of this solution?
1
O(x * y)
2
O(n × k)
3
O(log m * n)
4
O(4^L)
Why Not O(1) Space Like Paint House?
In the original Paint House with 3 colors, we optimized space from O(n) to O(1) by keeping just 3 variables instead of the full array. You might wonder: can we do the same here?
Unfortunately, no. With k colors, we need to store all k previous values because we're computing k new values simultaneously. Each new value needs to know which index had the minimum in the previous row, and we can't overwrite values we still need.
O(k) space is still much better than the naive O(n × k) space from storing the full 2D table.
Mark as read