LeetCode 2742 - Painting the Walls
The problem is asking us to determine the minimum total cost to paint n walls using two painters with different constraints. We are given two arrays: cost and time, both of size n.
Difficulty: 🔴 Hard
Topics: Array, Dynamic Programming
Solution
Problem Understanding
The problem is asking us to determine the minimum total cost to paint n walls using two painters with different constraints. We are given two arrays: cost and time, both of size n. The cost[i] represents the monetary cost of hiring the paid painter to paint the ith wall, and time[i] represents how long it will take the paid painter to paint that wall. There is also a free painter who can paint any wall in exactly 1 unit of time at zero cost, but the free painter can only paint walls while the paid painter is busy.
The goal is to schedule walls between the paid and free painter to minimize total monetary cost while satisfying the timing constraints.
Key constraints include:
- The number of walls
nis up to 500, making brute-force approaches with exponential complexity infeasible. - Individual
cost[i]can be as high as 10^6, andtime[i]can be up to 500. - Each wall must be painted exactly once.
Important edge cases to consider are when all walls have minimal or maximal times, when the cost array has extreme variations, or when the number of walls is very small (like 1).
Approaches
Brute Force
A brute-force approach would try every possible subset of walls to assign to the paid painter, computing the cost for that subset and verifying if the free painter can paint the remaining walls in parallel. This ensures correctness because we examine every assignment of walls, but it is too slow because there are 2^n possible subsets of walls, and n can be up to 500.
Key Insight for Optimization
The problem is essentially a variation of the 0-1 knapsack problem, but with a twist: the free painter introduces a time-based constraint. The paid painter's total time determines how many walls the free painter can complete simultaneously. Let total_time be the sum of the time units assigned to the paid painter. If total_time is at least n, all walls can be painted by the free painter in parallel. We need to select a subset of walls for the paid painter that minimizes total cost while ensuring the free painter's timing is sufficient.
This insight allows us to define a dynamic programming solution. Let dp[j] represent the minimum cost to achieve exactly j units of paid painter time. We iterate over each wall and update dp[j] as the minimum of including the current wall or skipping it. The final solution is the minimum dp[j] where j + free painter units >= n.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(2^n) | O(n) | Tries every subset of walls; exponential time infeasible for n=500 |
| Optimal (DP) | O(n * sum(time)) | O(sum(time)) | Uses 1D DP over total paid painter time; efficient for n≤500 and time[i]≤500 |
Algorithm Walkthrough
- Initialize a DP array
dpof lengthsum(time)+1, where each entry isinfexceptdp[0] = 0. This represents the minimum cost to achieve exactlyjunits of total paid painter time. - Iterate through each wall
i. For each wall, iteratejfromsum(time)down totime[i]. Updatedp[j]to be the minimum of its current value anddp[j - time[i]] + cost[i]. This ensures we consider using walliin the subset of paid walls without overwriting previous states prematurely. - After processing all walls, we look for the minimum
dp[j]such thatj + free_time >= n, wherefree_timeis the number of walls the free painter can paint simultaneously, which equals the total paid painter time because free painter paints 1 wall per unit of paid painter time. - Return the minimum cost found.
This works because dp[j] correctly tracks the minimum cost to accumulate j units of paid painter time, and the constraint j + free_time >= n ensures all walls can be painted.
Python Solution
from typing import List
class Solution:
def paintWalls(self, cost: List[int], time: List[int]) -> int:
n = len(cost)
max_time = sum(time)
dp = [float('inf')] * (max_time + 1)
dp[0] = 0
for i in range(n):
t = time[i]
c = cost[i]
for j in range(max_time, t - 1, -1):
dp[j] = min(dp[j], dp[j - t] + c)
res = float('inf')
for j in range(max_time + 1):
if j + (j) >= n: # free painter can cover the remaining walls
res = min(res, dp[j])
return res
The implementation initializes a DP array to infinity, except dp[0]. For each wall, we consider adding it to the total paid painter time while updating the cost. After processing, we check which total paid painter times allow the free painter to cover remaining walls and pick the minimum cost.
Go Solution
func paintWalls(cost []int, time []int) int {
n := len(cost)
maxTime := 0
for _, t := range time {
maxTime += t
}
dp := make([]int, maxTime+1)
for i := range dp {
dp[i] = 1 << 60
}
dp[0] = 0
for i := 0; i < n; i++ {
t := time[i]
c := cost[i]
for j := maxTime; j >= t; j-- {
if dp[j-t]+c < dp[j] {
dp[j] = dp[j-t] + c
}
}
}
res := 1 << 60
for j := 0; j <= maxTime; j++ {
if j+j >= n {
if dp[j] < res {
res = dp[j]
}
}
}
return res
}
In Go, we handle large numbers by initializing dp with a very large constant instead of inf. The logic otherwise mirrors Python: iterate backwards to prevent overwriting previous states.
Worked Examples
Example 1: cost = [1,2,3,2], time = [1,2,3,2]
| Step | Paid Painter Time | DP Array Update | Comment |
|---|---|---|---|
| 0 | 1 | dp[1] = 1 | Wall 0 painted by paid painter |
| 1 | 2 | dp[2] = 2, dp[3] = 3 | Wall 1 added |
| 2 | 3 | dp[3] = min(3, 1+3=4)=3, dp[4]=min(inf,2+3)=5, dp[5]=5+3=8 | Wall 2 considered |
| 3 | 2 | ... | Continue updating |
Check for j where j+j >= 4 (n=4), minimum cost is 3.
Example 2: cost = [2,3,4,2], time = [1,1,1,1]
DP builds combinations and the minimum cost achieving j+j >= 4 is 4.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n * sum(time)) | Each wall is processed for all possible cumulative times |
| Space | O(sum(time)) | DP array stores minimum cost for each possible total time |
This is efficient because sum(time) <= 500*500=250000, which is feasible for modern computation.
Test Cases
# Provided examples
assert Solution().paintWalls([1,2,3,2], [1,2,3,2]) == 3 # basic example
assert Solution().paintWalls([2,3,4,2], [1,1,1,1]) == 4 # all same time
# Edge cases
assert Solution().paintWalls([1], [1]) == 1 # single wall
assert Solution().paintWalls([10,1,1], [1,100,100]) == 1 # free painter helps reduce cost
assert Solution().paintWalls([1000000]*5, [500]*5) == 1000000*3 # maximum values
| Test | Why |
|---|---|
| [1,2,3,2],[1,2,3,2] | Basic scheduling and free painter usage |
| [2,3,4,2],[1,1,1,1] | Same time walls, need careful selection |
| [1],[1] | Minimal input |
| [10,1,1],[1,100,100] | Free painter reduces cost substantially |
| [1e6]*5,[500]*5 | Stress test with large values |
Edge Cases
One important edge case is when there is only one wall. The solution correctly assigns it to the paid painter, as the free painter cannot start without the paid painter being busy. Another edge case is when all time[i] are large; this ensures the DP array can handle cumulative times correctly. A