Minimum White Tiles After Covering With Carpets
HardPrompt
You are given a 0-indexed binary string floor, which represents the colors of tiles on a floor:
floor[i] = '0'denotes that theithtile of the floor is colored black.- On the other hand,
floor[i] = '1'denotes that theithtile of the floor is colored white.
You are also given numCarpets and carpetLen. You have numCarpets black carpets, each of length carpetLen tiles. Cover the tiles with the given carpets such that the number of white tiles still visible is minimum. Carpets may overlap one another.
Return the minimum number of white tiles still visible.
Example 1:
Input: floor = "10110101", numCarpets = 2, carpetLen = 2
Output: 2
Explanation:
The figure above shows one way of covering the tiles with the carpets such that only 2 white tiles are visible.
No other way of covering the tiles with the carpets can leave less than 2 white tiles visible.Example 2:
Input: floor = "11111", numCarpets = 2, carpetLen = 3
Output: 0
Explanation:
The figure above shows one way of covering the tiles with the carpets such that no white tiles are visible.
Note that the carpets are able to overlap one another.
Constraints:
1 <= carpetLen <= floor.length <= 1000floor[i]is either'0'or'1'.1 <= numCarpets <= 1000
Approaches
3 approaches with complexity analysis and trade-offs.
This approach involves a straightforward, brute-force recursion to explore all possible ways of placing the carpets. For each tile, we make a decision: either we place a carpet starting at this tile (if we have any left), or we don't. This creates a decision tree that explores all combinations. While simple to understand, its exponential nature makes it impractical for all but the smallest inputs.
Algorithm
- Define a recursive function, say
solve(index, carpetsLeft), that returns the minimum number of visible white tiles fromindexto the end of the floor, givencarpetsLeftcarpets. - Base Cases:
- If
indexis beyond the floor's end (index >= floor.length), no more tiles are left, so return 0. - If
carpetsLeftis 0, no more carpets can be used. Return the total number of white tiles fromindexto the end. This can be pre-calculated using a suffix sum array for efficiency.
- If
- Recursive Step: At
index, we have two choices:- Skip: Don't place a carpet starting at
index. The cost is 1 iffloor[index]is white, plus the result of the recursive call for the next tile:(floor[index] == '1' ? 1 : 0) + solve(index + 1, carpetsLeft). - Cover: Place a carpet starting at
index. This covers tiles up toindex + carpetLen - 1. The cost is the result of the recursive call for the state after the carpet ends:solve(index + carpetLen, carpetsLeft - 1).
- Skip: Don't place a carpet starting at
- The function returns the minimum of the results from these two choices.
Walkthrough
The core idea is to define a function that solves the problem for a suffix of the floor. Let's define solve(index, carpetsLeft) as the minimum white tiles visible from index onwards, with carpetsLeft available.
At each index, we face a choice:
- Don't place a carpet here: We move to the next tile
index + 1, and if the current tileindexis white, it adds to our count of visible white tiles. - Place a carpet here: We use one carpet, which covers the segment
[index, index + carpetLen - 1]. We then recursively solve for the subproblem starting atindex + carpetLenwith one less carpet.
The function returns the minimum of these two outcomes. The recursion stops when we run out of tiles or carpets.
class Solution { private String floor; private int n; private int carpetLen; private int[] suffixWhite; private int[][] memo; public int minimumWhiteTiles(String floor, int numCarpets, int carpetLen) { this.floor = floor; this.n = floor.length(); this.carpetLen = carpetLen; // Using memoization to show the transition to DP, but a pure recursive solution would omit this. // A naive solution without memoization would be too slow. this.memo = new int[n + 1][numCarpets + 1]; for (int[] row : memo) { java.util.Arrays.fill(row, -1); } // Precompute suffix sums of white tiles for the base case this.suffixWhite = new int[n + 1]; for (int i = n - 1; i >= 0; i--) { suffixWhite[i] = suffixWhite[i + 1] + (floor.charAt(i) - '0'); } return solve(0, numCarpets); } // This is the recursive helper. Without memoization, it's a naive approach. private int solve(int i, int k) { // Base cases if (i >= n) { return 0; } if (k == 0) { return suffixWhite[i]; } if (memo[i][k] != -1) { return memo[i][k]; } // Option 1: Skip tile i int skip = (floor.charAt(i) - '0') + solve(i + 1, k); // Option 2: Cover starting at tile i int cover = solve(i + carpetLen, k - 1); return memo[i][k] = Math.min(skip, cover); }}Complexity
Time
O(2^n) without memoization. The number of states can be large, and each state branches into two, leading to an exponential number of calls.
Space
O(n) for the recursion stack depth in the naive version. O(n * numCarpets) if memoization is added.
Trade-offs
Pros
Simple to conceptualize and follows the problem's decision-making process directly.
Cons
Extremely inefficient due to a massive number of redundant computations for the same subproblems.
Will result in a 'Time Limit Exceeded' error for the given constraints.
This approach uses dynamic programming to solve the problem efficiently by avoiding the redundant calculations of the naive recursion. We build a 2D table, dp[i][j], to store the optimal solution for subproblems. The state dp[i][j] represents the minimum number of visible white tiles considering the first i tiles of the floor and using j carpets. By building up the solution from smaller subproblems, we can find the global optimum.
Algorithm
- Create a 2D DP table,
dp[i][j], of size(n+1) x (numCarpets+1). dp[i][j]will store the minimum number of visible white tiles in the prefixfloor[0...i-1]usingjcarpets.- Initialization:
dp[0][j] = 0for allj(no tiles, no visible white tiles).dp[i][0]fori > 0is the number of white tiles infloor[0...i-1], as no carpets are used. This is calculated asdp[i-1][0] + (floor.charAt(i-1) - '0').
- Transition: Iterate through
ifrom 1 tonandjfrom 1 tonumCarpets.- For each
dp[i][j], we have two choices regarding the tilei-1:- Don't end a carpet at
i-1: The number of visible tiles is the result for the prefixi-1withjcarpets, plus one iffloor[i-1]is white. This isdp[i-1][j] + (floor.charAt(i-1) - '0'). - End a carpet at
i-1: This covers the range[i-carpetLen, i-1]. The number of visible tiles is determined by the state before this carpet, which isdp[i-carpetLen][j-1]. Ifi < carpetLen, we can cover all tiles from0toi-1, resulting in 0 visible tiles.
- Don't end a carpet at
- The recurrence relation is:
dp[i][j] = min(option1, option2).
- For each
- Result: The final answer is
dp[n][numCarpets].
Walkthrough
We define dp[i][j] as the minimum number of visible white tiles in the prefix floor[0...i-1] using j carpets. We can fill this table iteratively.
The base cases are straightforward:
- If we use 0 carpets (
j=0), the number of visible white tiles is simply the total count of white tiles in the prefix. - If we consider 0 tiles (
i=0), the number of visible white tiles is 0.
For the general case dp[i][j], we consider the i-th tile (at index i-1). We have two choices:
- We don't place a carpet that ends at
i-1. In this case, the solution is based on the subproblem for the firsti-1tiles withjcarpets, and we add 1 to the count iffloor[i-1]is white. - We place a carpet ending at
i-1. This carpet covers[i-carpetLen, i-1]. The number of visible tiles is then the solution for the subproblem on the firsti-carpetLentiles usingj-1carpets.
We take the minimum of these two choices.
class Solution { public int minimumWhiteTiles(String floor, int numCarpets, int carpetLen) { int n = floor.length(); int[][] dp = new int[n + 1][numCarpets + 1]; // dp[i][j]: min visible white tiles in floor[0...i-1] using j carpets for (int i = 1; i <= n; i++) { // Base case: j = 0 carpets dp[i][0] = dp[i - 1][0] + (floor.charAt(i - 1) - '0'); for (int j = 1; j <= numCarpets; j++) { // Option 1: Don't cover tile i-1 with a new carpet. int option1 = dp[i - 1][j] + (floor.charAt(i - 1) - '0'); // Option 2: Cover tiles [i-carpetLen, i-1] with a carpet. int option2; if (i >= carpetLen) { option2 = dp[i - carpetLen][j - 1]; } else { // If i < carpetLen, we can use one carpet to cover all tiles from 0 to i-1. option2 = 0; } dp[i][j] = Math.min(option1, option2); } } return dp[n][numCarpets]; }}Complexity
Time
O(n * numCarpets) because we iterate through each state of the `dp` table once.
Space
O(n * numCarpets) to store the 2D DP table.
Trade-offs
Pros
Guaranteed to find the optimal solution.
Efficient enough to pass the given constraints with a polynomial time complexity.
Cons
The space complexity of O(n * numCarpets) can be large if n and numCarpets are close to their maximum values (1000).
This is the most optimized approach. It builds upon the 2D DP solution by reducing its space complexity. By analyzing the dependencies in the DP recurrence relation, we notice that to compute the solutions for j carpets, we only need the solutions for j-1 carpets. This means we don't need to store the entire 2D DP table. We can get by with just two 1D arrays: one for the previous state (j-1 carpets) and one for the current state (j carpets). This reduces the space complexity from O(n * numCarpets) to O(n) while keeping the time complexity the same.
Algorithm
- Observe that the calculation of
dp[i][j]only depends on values from the current columnj(specificallydp[i-1][j]) and the previous columnj-1(specificallydp[i-carpetLen][j-1]). - This dependency allows us to optimize space. Instead of a 2D table, we can use two 1D arrays:
dpfor the current number of carpetsj, andprev_dpforj-1carpets. - Initialization:
- Create
dpandprev_dparrays of sizen+1. - Initialize
prev_dpto represent the case with 0 carpets:prev_dp[i]is the count of white tiles infloor[0...i-1].
- Create
- Transition: Iterate
jfrom 1 tonumCarpets.- Inside this loop, iterate
ifrom 1 tonto calculatedp[i]. dp[i] = min(dp[i-1] + (floor.charAt(i-1)-'0'), (i >= carpetLen ? prev_dp[i-carpetLen] : 0)).- After the inner loop (for
i) completes, thedparray holds the results forjcarpets. We then updateprev_dpwith the contents ofdpto prepare for the next iteration ofj.
- Inside this loop, iterate
- Result: After the outer loop finishes,
dp[n](orprev_dp[n]depending on implementation) will hold the final answer fornumCarpets.
Walkthrough
The recurrence relation for the 2D DP is dp[i][j] = min(dp[i-1][j] + ..., dp[i-carpetLen][j-1]). When we are calculating the values for j carpets, we only need values from the j-1 column. This suggests we can optimize the space.
We can use two 1D arrays: prev_dp to store the results for j-1 carpets, and dp to compute the results for j carpets. The loops are structured with the number of carpets j on the outside.
class Solution { public int minimumWhiteTiles(String floor, int numCarpets, int carpetLen) { int n = floor.length(); // dp[i] will store min visible tiles in floor[0...i-1] for the current number of carpets int[] dp = new int[n + 1]; // prev_dp[i] will store the results for one fewer carpet int[] prev_dp = new int[n + 1]; // Initialize for 0 carpets for (int i = 1; i <= n; i++) { prev_dp[i] = prev_dp[i - 1] + (floor.charAt(i - 1) - '0'); } // If no carpets, return the total number of white tiles if (numCarpets == 0) { return prev_dp[n]; } for (int j = 1; j <= numCarpets; j++) { dp[0] = 0; // For an empty prefix, visible tiles are 0 for (int i = 1; i <= n; i++) { // Option 1: Don't cover tile i-1 with a new carpet int option1 = dp[i - 1] + (floor.charAt(i - 1) - '0'); // Option 2: Cover tiles [i-carpetLen, i-1] with a carpet int option2; if (i >= carpetLen) { option2 = prev_dp[i - carpetLen]; } else { option2 = 0; } dp[i] = Math.min(option1, option2); } // The current dp becomes the prev_dp for the next iteration. // A pointer swap would be more efficient than copying, but this is clear. System.arraycopy(dp, 0, prev_dp, 0, n + 1); } // After the loop, prev_dp holds the result for numCarpets return prev_dp[n]; }}Complexity
Time
O(n * numCarpets), as the nested loop structure is preserved.
Space
O(n), as we only need two arrays of size `n+1`.
Trade-offs
Pros
Optimal space complexity, making it very memory-efficient.
Maintains the efficient time complexity of the 2D DP approach.
Cons
The implementation can be slightly more complex due to the need to manage and update two separate arrays.
Solutions
Solution
class Solution {private int[][] f;private int[] s;private int n;private int k;public int minimumWhiteTiles(String floor, int numCarpets, int carpetLen) { n = floor.length(); f = new int[n][numCarpets + 1]; for (var e : f) { Arrays.fill(e, -1); } s = new int[n + 1]; for (int i = 0; i < n; ++i) { s[i + 1] = s[i] + (floor.charAt(i) == '1' ? 1 : 0); } k = carpetLen; return dfs(0, numCarpets); }private int dfs(int i, int j) { if (i >= n) { return 0; } if (j == 0) { return s[n] - s[i]; } if (f[i][j] != -1) { return f[i][j]; } if (s[i + 1] == s[i]) { return dfs(i + 1, j); } int ans = Math.min(1 + dfs(i + 1, j), dfs(i + k, j - 1)); f[i][j] = ans; return ans; }}class Solution { public: int minimumWhiteTiles ( string floor , int numCarpets , int carpetLen ) { int n = floor . size (); vector < vector < int >> f ( n , vector < int > ( numCarpets + 1 , - 1 )); vector < int > s ( n + 1 ); for ( int i = 0 ; i < n ; ++ i ) { s [ i + 1 ] = s [ i ] + ( floor [ i ] == '1' ); } function < int ( int , int ) > dfs ; dfs = [ & ]( int i , int j ) { if ( i >= n ) return 0 ; if ( j == 0 ) return s [ n ] - s [ i ]; if ( f [ i ][ j ] != - 1 ) return f [ i ][ j ]; if ( s [ i + 1 ] == s [ i ]) return dfs ( i + 1 , j ); int ans = min ( 1 + dfs ( i + 1 , j ), dfs ( i + carpetLen , j - 1 )); f [ i ][ j ] = ans ; return ans ; }; return dfs ( 0 , numCarpets ); } };class Solution: def minimumWhiteTiles(self, floor: str, numCarpets: int, carpetLen: int) -> int: @ cache def dfs(i, j): if i >= n: return 0 if floor[i] == '0': return dfs(i + 1, j) if j == 0: return s[- 1] - s[i] return min(1 + dfs(i + 1, j), dfs(i + carpetLen, j - 1)) n = len(floor) s = [0] * (n + 1) for i, c in enumerate(floor): s[i + 1] = s[i] + int(c == '1') ans = dfs(0, numCarpets) dfs . cache_clear() return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.