Skip to content

Commit 9fa0e94

Browse files
committed
feat(algorithms, dynamic-programming): min cost to paint houses
1 parent 434692c commit 9fa0e94

File tree

3 files changed

+170
-0
lines changed

3 files changed

+170
-0
lines changed
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Paint House
2+
3+
You are a renowned painter who is given a task to paint n houses in a row. You can paint each house with one of three colors: Red, Blue, or Green. The cost of painting each house with each color is different and given in a 2D array costs:
4+
5+
- costs[i][0] = cost of painting house `i` Red
6+
- costs[i][1] = cost of painting house `i` Blue
7+
- costs[i][2] = cost of painting house `i` Green
8+
9+
No two neighboring houses can have the same color. Return the minimum cost to paint all houses.
10+
11+
## Constraints
12+
13+
- 1 ≤ n ≤ 100
14+
- costs[i].length == 3
15+
- 1 ≤ costs[i][j] ≤ 1000
16+
17+
## Examples
18+
19+
Example 1
20+
21+
```text
22+
costs = [[8, 4, 15], [10, 7, 3], [6, 9, 12]]
23+
Output: 13
24+
25+
Explanation:
26+
27+
House 0: Blue (cost = 4)
28+
House 1: Green (cost = 3)
29+
House 2: Red (cost = 6)
30+
Total = 4 + 3 + 6 = 13
31+
32+
```
33+
34+
Example 2
35+
```text
36+
Input:
37+
38+
costs = [[5, 8, 6], [19, 14, 13], [7, 5, 12], [14, 5, 9]]
39+
Output: 30
40+
41+
Explanation: Red(5) → Green(13) → Red(7) → Blue(5) = 30
42+
```
43+
44+
## Solution
45+
46+
The solution implements a space-optimized dynamic programming approach using three variables to track the minimum costs.
47+
48+
Variable Initialization:
49+
50+
prev_min_cost_red = 0: Minimum cost if the current house is painted red (color 0)
51+
prev_min_cost_blue = 0: Minimum cost if the current house is painted blue (color 1)
52+
prev_min_cost_green = 0: Minimum cost if the current house is painted green (color 2)
53+
54+
These start at 0 because before painting any house, the cost is 0.
55+
56+
Main Loop: The algorithm iterates through each house's costs using tuple unpacking:
57+
58+
```python
59+
for cost_red, cost_blue, cost_green in costs:
60+
#
61+
```
62+
63+
Where cost_red, cost_blue, cost_green represent the costs of painting the current house with red, blue, and green
64+
respectively.
65+
66+
State Transition: For each house, we simultaneously update all three variables:
67+
68+
```python
69+
prev_min_cost_red, prev_min_cost_blue, prev_min_cost_green = min(prev_min_cost_blue, prev_min_cost_green) + cost_red,
70+
min(prev_min_cost_red, prev_min_cost_green) + cost_blue, min(prev_min_cost_red, prev_min_cost_blue) + cost_green
71+
```
72+
73+
Breaking this down:
74+
75+
- New `prev_min_cost_red` (cost if current house is red): `min(prev_min_cost_blue, prev_min_cost_gree) + cost_red`
76+
We take the minimum of the previous costs where the house was NOT red (either blue or green)
77+
Add the cost of painting the current house red
78+
79+
- New `prev_min_cost_blue` (cost if current house is blue): `min(prev_min_cost_red, prev_min_cost_green) + cost_blue`
80+
We take the minimum of the previous costs where the house was NOT blue (either red or green)
81+
Add the cost of painting the current house blue
82+
83+
- New `prev_min_cost_green` (cost if current house is green): `min(prev_min_cost_red, prev_min_cost_blue) + cost_blue`
84+
We take the minimum of the previous costs where the house was NOT green (either red or blue)
85+
Add the cost of painting the current house green
86+
87+
The simultaneous assignment is crucial here - all three values are calculated using the old values before any updates occur.
88+
89+
Final Result: After processing all houses, `prev_min_cost_red`, `prev_min_cost_blue`, and `prev_min_cost_green` contain
90+
the minimum costs to paint all houses with the last house being red, blue, or green respectively. The answer is the
91+
minimum among these three values:
92+
93+
`return min(prev_mins_cost_red, prev_min_cost_blue, prev_min_cost_green)`
94+
95+
### Complexity Analysis
96+
97+
#### Time Complexity: O(n)
98+
99+
Where n is the number of houses (length of the costs array). The algorithm iterates through the costs array exactly once,
100+
performing constant-time operations (comparisons and additions) for each house.
101+
102+
#### Space Complexity: O(1)
103+
104+
The algorithm uses only three variables (`prev_min_cost_red`, `prev_min_cost_blue`, `prev_min_cost_green`) to track the
105+
minimum costs regardless of the input size. The space usage remains constant as it doesn't create any additional data
106+
structures that scale with the input. The variables are reused and updated in each iteration rather than storing all
107+
intermediate results.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from typing import List
2+
3+
4+
def min_cost_to_paint_houses_alternate_colors(costs: List[List[int]]) -> int:
5+
"""
6+
Finds the minimum cost to paint houses in a street without repeating colors consecutively
7+
Args:
8+
costs(list): Costs of painting houses as a 2D list
9+
Returns:
10+
int: minimum cost of painting houses
11+
"""
12+
# If there are no costs of painting the houses, then the minimum cost is 0
13+
if not costs:
14+
return 0
15+
16+
# initially, the cost of painting any house any color is 0
17+
# Initialize minimum costs for painting up to previous house with each color
18+
# prev_min_cost_red: min cost if previous house painted red
19+
# prev_min_cost_blue: min cost if previous house painted blue
20+
# prev_min_cost_green: min cost if previous house painted green
21+
prev_min_cost_red = 0
22+
prev_min_cost_blue = 0
23+
prev_min_cost_green = 0
24+
25+
# Iterate through each house cost
26+
for current_cost in costs:
27+
cost_red, cost_blue, cost_green = current_cost
28+
29+
# Calculate minimum cost for current house with each color
30+
# Current house painted red: add red cost to min of (prev blue, prev green)
31+
# Current house painted blue: add blue cost to min of (prev red, prev green)
32+
# Current house painted green: add green cost to min of (prev red, prev blue)
33+
curr_min_cost_red = min(prev_min_cost_blue, prev_min_cost_green) + cost_red
34+
curr_min_cost_blue = min(prev_min_cost_red, prev_min_cost_green) + cost_blue
35+
curr_min_cost_green = min(prev_min_cost_red, prev_min_cost_blue) + cost_green
36+
37+
# Update previous costs for next iteration
38+
prev_min_cost_red = curr_min_cost_red
39+
prev_min_cost_blue = curr_min_cost_blue
40+
prev_min_cost_green = curr_min_cost_green
41+
42+
# Return minimum cost among all three color options for the last house
43+
return min(prev_min_cost_red, prev_min_cost_blue, prev_min_cost_green)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import unittest
2+
from typing import List
3+
from parameterized import parameterized
4+
from algorithms.dynamic_programming.painthouse import min_cost_to_paint_houses_alternate_colors
5+
6+
MIN_COST_PAINT_HOUSE = [
7+
([[8, 4, 15], [10, 7, 3], [6, 9, 12]], 13),
8+
([[5, 8, 6], [19, 14, 13], [7, 5, 12], [14, 5, 9]], 30),
9+
]
10+
11+
12+
class MinCostToPaintHouseTestCase(unittest.TestCase):
13+
@parameterized.expand(MIN_COST_PAINT_HOUSE)
14+
def test_min_cost_to_paint_houses(self, cost: List[List[int]], expected: int):
15+
actual = min_cost_to_paint_houses_alternate_colors(cost)
16+
self.assertEqual(expected, actual)
17+
18+
19+
if __name__ == '__main__':
20+
unittest.main()

0 commit comments

Comments
 (0)