2271. Maximum White Tiles Covered by a Carpet
Problem Description
You have a collection of tile segments on a number line, where each segment represents a continuous range of white tiles. Each tile segment is given as [l, r]
, meaning all integer positions from l
to r
(inclusive) contain white tiles.
You also have a carpet of fixed length carpetLen
that you can place at any position on the number line. The carpet will cover exactly carpetLen
consecutive positions starting from wherever you place it.
Your goal is to find the optimal placement of the carpet such that it covers the maximum number of white tiles possible. The carpet can partially cover tile segments - it doesn't need to align perfectly with segment boundaries.
For example, if you have tiles at positions [1, 2]
and [5, 7]
with a carpet of length 4, you could place the carpet starting at position 1 to cover tiles 1 and 2 (covering 2 tiles total), or starting at position 4 to cover tiles 5, 6, and 7 (covering 3 tiles total).
The function should return the maximum number of white tiles that can be covered by placing the carpet optimally.
Intuition
The key insight is that to maximize coverage, we should always start the carpet at the beginning of some tile segment. Why? If the carpet starts in empty space before a segment, we can shift it forward to the segment's start without losing any coverage while potentially gaining more tiles.
Once we realize the carpet should start at a segment's beginning, we can use a sliding window approach. For each potential starting position (the left edge of each tile segment), we need to determine how many tiles the carpet can cover from that position.
The challenge is efficiently calculating coverage when the carpet might partially overlap with a segment. If a carpet of length carpetLen
starts at position li
(left edge of segment i
), it extends to position li + carpetLen - 1
. We need to count:
- All complete segments that fall entirely within the carpet's range
- Any partial coverage of a segment that extends beyond the carpet's right edge
This naturally leads to a two-pointer technique:
- The first pointer
i
iterates through each segment as a potential starting position - The second pointer
j
tracks which segments are fully or partially covered by the carpet
As we move the starting position from one segment to the next, we can maintain a running sum s
of fully covered segments. When we encounter a segment that's only partially covered (the carpet ends somewhere in the middle of it), we calculate the partial coverage as li + carpetLen - tiles[j][0]
, which represents how many tiles from that segment fall within the carpet's range.
The sliding window works because as we move to later starting positions, we remove the contribution of earlier segments that are no longer covered and add new segments that come into range, avoiding redundant recalculation.
Learn more about Greedy, Binary Search, Prefix Sum, Sorting and Sliding Window patterns.
Solution Approach
The implementation uses a sliding window with two pointers to efficiently find the maximum coverage:
Step 1: Sort the tiles
tiles.sort()
First, we sort the tiles by their starting positions to process them in order from left to right.
Step 2: Initialize variables
s
: Running sum of tiles in fully covered segmentsans
: Maximum tiles covered so farj
: Right pointer tracking which segments are within carpet rangei
: Left pointer iterating through each segment as a potential carpet starting position
Step 3: Main sliding window logic
For each segment i
with left edge li
and right edge ri
:
while j < n and tiles[j][1] - li + 1 <= carpetLen: s += tiles[j][1] - tiles[j][0] + 1 j += 1
This loop adds all segments that can be fully covered when the carpet starts at li
. The condition tiles[j][1] - li + 1 <= carpetLen
checks if segment j
's right edge is within the carpet's reach (carpet extends from li
to li + carpetLen - 1
).
Step 4: Handle partial coverage
if j < n and li + carpetLen > tiles[j][0]:
ans = max(ans, s + li + carpetLen - tiles[j][0])
else:
ans = max(ans, s)
- If there's a segment
j
that's partially covered (carpet ends somewhere within it), we add the partial coverage:li + carpetLen - tiles[j][0]
- Otherwise, only fully covered segments contribute to the count
Step 5: Slide the window
s -= ri - li + 1
Before moving to the next starting position, we remove the current segment i
from the running sum since it will no longer be covered when we start the carpet at segment i+1
.
The algorithm maintains the invariant that s
contains the sum of all fully covered segments for the current carpet position, while j
always points to the first segment that's either partially covered or completely outside the carpet's range.
Time Complexity: O(n log n)
for sorting plus O(n)
for the sliding window, giving O(n log n)
overall.
Space Complexity: O(1)
excluding the sorting space.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to illustrate the solution approach.
Input: tiles = [[1, 2], [5, 7], [9, 10]]
, carpetLen = 4
Step 1: Initial Setup
- After sorting (already sorted):
tiles = [[1, 2], [5, 7], [9, 10]]
- Initialize:
s = 0
,ans = 0
,j = 0
,n = 3
Step 2: Try carpet starting at position 1 (segment i=0)
- Carpet covers positions 1 to 4 (since
carpetLen = 4
) - Check which segments can be fully covered:
- Segment [1, 2]: length = 2, distance from start = 2-1+1 = 2 ≤ 4 ✓ (fully covered)
- Add to sum:
s = 0 + 2 = 2
, movej
to 1
- Add to sum:
- Segment [5, 7]: length = 3, distance from start = 7-1+1 = 7 > 4 ✗ (not fully covered)
- Stop the while loop
- Segment [1, 2]: length = 2, distance from start = 2-1+1 = 2 ≤ 4 ✓ (fully covered)
- Check partial coverage of segment j=1 ([5, 7]):
- Carpet ends at position 4, segment starts at 5
- No partial coverage (4 < 5)
- Update answer:
ans = max(0, 2) = 2
- Slide window: Remove segment i=0 from sum:
s = 2 - 2 = 0
Step 3: Try carpet starting at position 5 (segment i=1)
- Carpet covers positions 5 to 8
- Check which segments can be fully covered:
- Segment [5, 7]: length = 3, distance from start = 7-5+1 = 3 ≤ 4 ✓ (fully covered)
- Add to sum:
s = 0 + 3 = 3
, movej
to 2
- Add to sum:
- Segment [9, 10]: length = 2, distance from start = 10-5+1 = 6 > 4 ✗ (not fully covered)
- Stop the while loop
- Segment [5, 7]: length = 3, distance from start = 7-5+1 = 3 ≤ 4 ✓ (fully covered)
- Check partial coverage of segment j=2 ([9, 10]):
- Carpet ends at position 8, segment starts at 9
- No partial coverage (8 < 9)
- Update answer:
ans = max(2, 3) = 3
- Slide window: Remove segment i=1 from sum:
s = 3 - 3 = 0
Step 4: Try carpet starting at position 9 (segment i=2)
- Carpet covers positions 9 to 12
- Check which segments can be fully covered:
- Segment [9, 10]: length = 2, distance from start = 10-9+1 = 2 ≤ 4 ✓ (fully covered)
- Add to sum:
s = 0 + 2 = 2
, movej
to 3
- Add to sum:
- Segment [9, 10]: length = 2, distance from start = 10-9+1 = 2 ≤ 4 ✓ (fully covered)
- No more segments to check
- Update answer:
ans = max(3, 2) = 3
Final Answer: 3
The optimal placement is starting the carpet at position 5, which covers all 3 tiles in segment [5, 7].
Solution Implementation
1class Solution:
2 def maximumWhiteTiles(self, tiles: List[List[int]], carpetLen: int) -> int:
3 # Sort tiles by starting position for sequential processing
4 tiles.sort()
5 num_tiles = len(tiles)
6
7 # Initialize variables for sliding window approach
8 current_coverage = 0 # Total tiles covered in current window
9 max_coverage = 0 # Maximum tiles covered so far
10 right_index = 0 # Right pointer for the sliding window
11
12 # Iterate through each tile as a potential starting position for the carpet
13 for left_index, (left_bound, right_bound) in enumerate(tiles):
14 # Expand window: include all tiles that can be fully covered
15 # when carpet starts at current tile's left boundary
16 while right_index < num_tiles and tiles[right_index][1] - left_bound + 1 <= carpetLen:
17 # Add the entire tile to current coverage
18 current_coverage += tiles[right_index][1] - tiles[right_index][0] + 1
19 right_index += 1
20
21 # Check if carpet can partially cover the next tile
22 if right_index < num_tiles and left_bound + carpetLen > tiles[right_index][0]:
23 # Calculate coverage including partial coverage of next tile
24 # Partial coverage = carpet_end - next_tile_start
25 partial_coverage = left_bound + carpetLen - tiles[right_index][0]
26 max_coverage = max(max_coverage, current_coverage + partial_coverage)
27 else:
28 # No partial coverage possible, use full coverage of current window
29 max_coverage = max(max_coverage, current_coverage)
30
31 # Shrink window: remove the leftmost tile as we move to next starting position
32 current_coverage -= right_bound - left_bound + 1
33
34 return max_coverage
35
1class Solution {
2 public int maximumWhiteTiles(int[][] tiles, int carpetLen) {
3 // Sort tiles by starting position in ascending order
4 Arrays.sort(tiles, (a, b) -> a[0] - b[0]);
5
6 int tilesCount = tiles.length;
7 int currentCoverage = 0; // Current sum of tiles covered by the carpet
8 int maxCoverage = 0; // Maximum tiles coverage found so far
9
10 // Use sliding window approach with left pointer i and right pointer j
11 for (int left = 0, right = 0; left < tilesCount; left++) {
12 // Expand window: add complete tiles that fit entirely within carpet length
13 // starting from tiles[left][0]
14 while (right < tilesCount &&
15 tiles[right][1] - tiles[left][0] + 1 <= carpetLen) {
16 // Add the length of current tile to coverage
17 currentCoverage += tiles[right][1] - tiles[right][0] + 1;
18 right++;
19 }
20
21 // Check if carpet can partially cover the next tile
22 if (right < tilesCount &&
23 tiles[left][0] + carpetLen > tiles[right][0]) {
24 // Carpet extends beyond start of tiles[right] but doesn't cover it completely
25 // Calculate coverage including partial coverage of tiles[right]
26 int partialCoverage = tiles[left][0] + carpetLen - tiles[right][0];
27 maxCoverage = Math.max(maxCoverage, currentCoverage + partialCoverage);
28 } else {
29 // Either no more tiles or carpet doesn't reach next tile
30 // Update max with current complete coverage
31 maxCoverage = Math.max(maxCoverage, currentCoverage);
32 }
33
34 // Shrink window: remove the leftmost tile before moving left pointer
35 currentCoverage -= tiles[left][1] - tiles[left][0] + 1;
36 }
37
38 return maxCoverage;
39 }
40}
41
1class Solution {
2public:
3 int maximumWhiteTiles(vector<vector<int>>& tiles, int carpetLen) {
4 // Sort tiles by their starting positions
5 sort(tiles.begin(), tiles.end());
6
7 int coveredTiles = 0; // Current number of tiles covered in the window
8 int maxCovered = 0; // Maximum tiles that can be covered
9 int numTiles = tiles.size();
10
11 // Use two pointers to maintain a sliding window
12 int left = 0; // Left boundary of the carpet placement
13 int right = 0; // Right boundary tile index
14
15 // Try placing carpet starting from each tile's left edge
16 for (left = 0; left < numTiles; ++left) {
17 // Expand window: include all tiles fully within carpet range
18 while (right < numTiles &&
19 tiles[right][1] - tiles[left][0] + 1 <= carpetLen) {
20 // Add all tiles in current range to covered count
21 coveredTiles += tiles[right][1] - tiles[right][0] + 1;
22 ++right;
23 }
24
25 // Check if carpet partially covers the next tile
26 if (right < numTiles &&
27 tiles[left][0] + carpetLen > tiles[right][0]) {
28 // Calculate partial coverage of the next tile
29 int partialCoverage = tiles[left][0] + carpetLen - tiles[right][0];
30 maxCovered = max(maxCovered, coveredTiles + partialCoverage);
31 } else {
32 // All tiles in range are fully covered
33 maxCovered = max(maxCovered, coveredTiles);
34 }
35
36 // Shrink window: remove leftmost tile for next iteration
37 coveredTiles -= (tiles[left][1] - tiles[left][0] + 1);
38 }
39
40 return maxCovered;
41 }
42};
43
1function maximumWhiteTiles(tiles: number[][], carpetLen: number): number {
2 // Sort tiles by their starting positions
3 tiles.sort((a, b) => a[0] - b[0]);
4
5 let coveredTiles = 0; // Current number of tiles covered in the window
6 let maxCovered = 0; // Maximum tiles that can be covered
7 const numTiles = tiles.length;
8
9 // Use two pointers to maintain a sliding window
10 let left = 0; // Left boundary of the carpet placement
11 let right = 0; // Right boundary tile index
12
13 // Try placing carpet starting from each tile's left edge
14 for (left = 0; left < numTiles; left++) {
15 // Expand window: include all tiles fully within carpet range
16 while (right < numTiles &&
17 tiles[right][1] - tiles[left][0] + 1 <= carpetLen) {
18 // Add all tiles in current range to covered count
19 coveredTiles += tiles[right][1] - tiles[right][0] + 1;
20 right++;
21 }
22
23 // Check if carpet partially covers the next tile
24 if (right < numTiles &&
25 tiles[left][0] + carpetLen > tiles[right][0]) {
26 // Calculate partial coverage of the next tile
27 const partialCoverage = tiles[left][0] + carpetLen - tiles[right][0];
28 maxCovered = Math.max(maxCovered, coveredTiles + partialCoverage);
29 } else {
30 // All tiles in range are fully covered
31 maxCovered = Math.max(maxCovered, coveredTiles);
32 }
33
34 // Shrink window: remove leftmost tile for next iteration
35 coveredTiles -= (tiles[left][1] - tiles[left][0] + 1);
36 }
37
38 return maxCovered;
39}
40
Time and Space Complexity
Time Complexity: O(n log n)
where n
is the number of tile intervals.
- The sorting operation takes
O(n log n)
time. - The main loop iterates through each tile interval with index
i
from0
ton-1
, which isO(n)
. - The inner while loop might seem like it creates nested iteration, but each tile interval is visited by the pointer
j
at most once throughout the entire execution. The pointerj
only moves forward and never resets, making it a two-pointer technique with amortizedO(n)
complexity. - Inside the loops, all operations (comparisons, arithmetic operations, max calculations) are
O(1)
. - Therefore, the overall time complexity is dominated by the sorting:
O(n log n)
.
Space Complexity: O(1)
if we don't count the space used by sorting, or O(n)
if we consider the space used by the sorting algorithm.
- The algorithm uses a constant amount of extra variables:
n
,s
,ans
,j
,i
,li
, andri
. - No additional data structures are created that scale with the input size.
- However, the
sort()
method in Python uses Timsort, which requiresO(n)
auxiliary space in the worst case. - If we consider only the extra space used by the algorithm itself (excluding sorting), the space complexity is
O(1)
. - If we include the sorting space overhead, the space complexity is
O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Off-by-One Error in Carpet Coverage Calculation
A frequent mistake is incorrectly calculating whether a tile segment fits entirely within the carpet's range. The condition tiles[j][1] - li + 1 <= carpetLen
includes the "+1" because both endpoints are inclusive.
Incorrect approach:
# Wrong: Missing the +1 while j < n and tiles[j][1] - li <= carpetLen:
Why it fails: This would incorrectly include segments that extend beyond the carpet. For example, if carpet starts at position 5 with length 3 (covering positions 5, 6, 7), and a segment is [5, 8], the wrong condition would evaluate to 8 - 5 = 3 <= 3
(True), incorrectly assuming the entire segment fits.
Correct approach:
# Correct: Include +1 for inclusive range while j < n and tiles[j][1] - li + 1 <= carpetLen:
2. Forgetting to Handle Partial Coverage Edge Cases
Another pitfall is only considering fully covered segments and ignoring partial coverage of the next segment that extends beyond the carpet.
Incorrect approach:
# Wrong: Only considering fully covered segments
for left_index, (left_bound, right_bound) in enumerate(tiles):
while right_index < num_tiles and tiles[right_index][1] - left_bound + 1 <= carpetLen:
current_coverage += tiles[right_index][1] - tiles[right_index][0] + 1
right_index += 1
# Missing partial coverage calculation!
max_coverage = max(max_coverage, current_coverage)
Why it fails: This misses cases where the optimal solution involves partial coverage. For instance, with tiles [[1,1], [10,14]] and carpetLen=5, starting the carpet at position 10 covers all 5 tiles in the second segment, but without partial coverage logic, we'd only consider the single tile at position 1.
Correct approach:
# Check for partial coverage of the next segment
if right_index < num_tiles and left_bound + carpetLen > tiles[right_index][0]:
partial_coverage = left_bound + carpetLen - tiles[right_index][0]
max_coverage = max(max_coverage, current_coverage + partial_coverage)
else:
max_coverage = max(max_coverage, current_coverage)
3. Not Maintaining the Sliding Window Properly
Failing to remove the current segment from current_coverage
when sliding the window forward can lead to overcounting.
Incorrect approach:
for left_index, (left_bound, right_bound) in enumerate(tiles):
# ... expansion logic ...
# Wrong: Forgetting to remove the current segment
# current_coverage accumulates incorrectly
Why it fails: Without removing the leftmost segment, current_coverage
keeps accumulating tiles that are no longer covered when the carpet moves to the next starting position, leading to incorrect maximum calculations.
Correct approach:
# Remove the current segment before moving to the next iteration current_coverage -= right_bound - left_bound + 1
4. Assuming Carpet Must Start at a Tile Position
While the algorithm correctly starts the carpet at each segment's left boundary (which is optimal), a conceptual pitfall is thinking this is the only valid approach. The algorithm works because starting at any position between segments would cover fewer tiles than starting at the next segment's beginning.
Key insight: Starting the carpet at each segment's left boundary is sufficient to find the optimal solution - we don't need to check every possible integer position on the number line.
What's the relationship between a tree and a graph?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
Want a Structured Path to Master System Design Too? Don’t Miss This!