LeetCode 1943 - Describe the Painting
The problem describes a painting laid out on a number line. Each segment of the painting is represented as a half-closed interval [start, end) and is painted with a unique color value.
Difficulty: 🟡 Medium
Topics: Array, Hash Table, Sorting, Prefix Sum
Solution
Problem Understanding
The problem describes a painting laid out on a number line. Each segment of the painting is represented as a half-closed interval [start, end) and is painted with a unique color value. Since multiple segments may overlap, different colors can exist simultaneously over the same region.
Instead of returning the actual set of colors for each region, we only need to return the sum of the colors currently active on that interval.
The goal is to split the entire painted line into the minimum number of non-overlapping half-closed intervals such that every interval has a constant mixed color sum.
A key detail is that intervals are half-closed. The segment [a, b) includes a but excludes b. This means when one segment ends exactly where another begins, they do not overlap at that point.
The input is:
segments[i] = [starti, endi, colori]
where:
startiis the inclusive start of the segmentendiis the exclusive endcoloriis the unique color assigned to that segment
The output should be:
[left, right, mixedColorSum]
for every continuous painted region where the mixed color sum remains constant.
The constraints are important:
- Up to
2 * 10^4segments - Coordinates up to
10^5 - Colors up to
10^9
A brute-force simulation over every coordinate would be too slow if implemented naively, especially because coordinates can be large and colors are large integers.
Several edge cases are important:
- Multiple segments may start or end at the same coordinate
- Adjacent intervals can have the same color sum but still represent different color sets
- Some regions may become unpainted and should not appear in the answer
- Segments can overlap partially or completely
- Coordinates are sparse, so iterating over every integer point is unnecessary
The fact that colors are unique is also useful. It guarantees that adding and removing colors can be handled cleanly using prefix-sum style updates.
Approaches
Brute Force Approach
A naive solution would simulate the painting across the entire number line.
We could create an array representing every coordinate position from 0 to 10^5. For every segment [start, end, color], we would iterate through all positions in that interval and add color to that position.
After processing all segments, we would scan the array and merge consecutive positions with the same color sum.
This approach is correct because every unit interval would explicitly store the exact mixed color sum active there.
However, it is inefficient because every segment may cover up to 10^5 positions. With 2 * 10^4 segments, the worst-case runtime becomes too large.
Optimal Approach
The key observation is that the mixed color sum only changes at segment boundaries.
If no segment starts or ends between two coordinates, then the active set of colors remains unchanged throughout that interval.
This means we do not need to process every coordinate individually. Instead, we can use a sweep line algorithm with prefix-sum style events.
For every segment:
- Add
+coloratstart - Add
-coloratend
Then we sort all event coordinates and sweep from left to right while maintaining the current active color sum.
Between two consecutive coordinates:
- If the active sum is nonzero, that interval contributes to the answer
- The active sum remains constant throughout that interval
This reduces the problem to processing only boundary points instead of every coordinate.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n * maxCoordinate) | O(maxCoordinate) | Explicitly paints every coordinate |
| Optimal | O(n log n) | O(n) | Sweep line using difference events |
Algorithm Walkthrough
- Create a difference map.
We use a hash map where each coordinate stores how the active color sum changes at that point.
For every segment [start, end, color]:
- Add
coloratstart - Subtract
coloratend
Example:
[1,4,5]
produces:
diff[1] += 5
diff[4] -= 5
- Sort all event coordinates.
The active mixed color can only change at these coordinates, so we process them in increasing order. 3. Sweep through the sorted coordinates.
Maintain:
current_sum, the active mixed color sumprev_position, the previous coordinate
- Before applying the update at the current coordinate, create an interval.
Suppose we move from prev_position to position.
If current_sum > 0, then the interval:
[prev_position, position)
has a constant mixed color sum equal to current_sum.
Add it to the answer. 5. Apply the difference update.
Update:
current_sum += diff[position]
This activates or deactivates colors beginning at this coordinate. 6. Continue until all coordinates are processed.
Since the last coordinate has no interval after it, the sweep naturally terminates.
Why it works
The algorithm relies on the invariant that between two consecutive event coordinates, the set of active segments never changes.
Since colors only start or end at event points, the mixed color sum remains constant throughout the entire interval between them.
By maintaining the running sum of active colors while sweeping left to right, we correctly reconstruct every maximal interval with a constant mixed color sum.
Python Solution
from typing import List
from collections import defaultdict
class Solution:
def splitPainting(self, segments: List[List[int]]) -> List[List[int]]:
diff = defaultdict(int)
for start, end, color in segments:
diff[start] += color
diff[end] -= color
sorted_positions = sorted(diff.keys())
result = []
current_sum = 0
prev_position = sorted_positions[0]
for position in sorted_positions:
if current_sum > 0 and prev_position < position:
result.append([prev_position, position, current_sum])
current_sum += diff[position]
prev_position = position
return result
The implementation begins by constructing the difference map. Every segment contributes a positive update at its start and a negative update at its end.
The coordinates where changes occur are then sorted. These coordinates define all possible interval boundaries.
The sweep line traversal maintains the currently active mixed color sum. Before applying updates at the current coordinate, the algorithm records the interval between the previous coordinate and the current coordinate, because the active color set remained unchanged throughout that region.
The condition:
current_sum > 0
ensures that unpainted intervals are excluded from the result.
The condition:
prev_position < position
prevents zero-length intervals.
Finally, after processing all coordinates, the constructed list is returned.
Go Solution
package main
import (
"sort"
)
func splitPainting(segments [][]int) [][]int64 {
diff := make(map[int]int64)
for _, segment := range segments {
start := segment[0]
end := segment[1]
color := int64(segment[2])
diff[start] += color
diff[end] -= color
}
positions := make([]int, 0, len(diff))
for pos := range diff {
positions = append(positions, pos)
}
sort.Ints(positions)
result := [][]int64{}
var currentSum int64 = 0
prevPosition := positions[0]
for _, position := range positions {
if currentSum > 0 && prevPosition < position {
result = append(result, []int64{
int64(prevPosition),
int64(position),
currentSum,
})
}
currentSum += diff[position]
prevPosition = position
}
return result
}
The Go implementation follows the same sweep-line logic as the Python version.
One important difference is the use of int64. Since colors can be as large as 10^9 and there may be many overlapping segments, the total sum can exceed the range of a standard 32-bit integer.
Go maps also require explicit initialization with make, unlike Python's defaultdict.
The result type is [][]int64 because the LeetCode signature expects 64-bit integers.
Worked Examples
Example 1
Input:
segments = [[1,4,5],[4,7,7],[1,7,9]]
Step 1: Build Difference Map
| Coordinate | Change |
|---|---|
| 1 | +5 |
| 4 | -5 +7 |
| 7 | -7 |
| 1 | +9 |
| 7 | -9 |
Combined:
| Coordinate | Net Change |
|---|---|
| 1 | +14 |
| 4 | +2 |
| 7 | -16 |
Step 2: Sort Coordinates
[1, 4, 7]
Step 3: Sweep
| Current Position | Previous Position | Current Sum Before Update | Interval Added | Update Applied | New Sum |
|---|---|---|---|---|---|
| 1 | 1 | 0 | none | +14 | 14 |
| 4 | 1 | 14 | [1,4,14] | +2 | 16 |
| 7 | 4 | 16 | [4,7,16] | -16 | 0 |
Final answer:
[[1,4,14],[4,7,16]]
Example 2
Input:
segments = [[1,7,9],[6,8,15],[8,10,7]]
Difference Map
| Coordinate | Net Change |
|---|---|
| 1 | +9 |
| 6 | +15 |
| 7 | -9 |
| 8 | -15 +7 |
| 10 | -7 |
Simplified:
| Coordinate | Net Change |
|---|---|
| 1 | +9 |
| 6 | +15 |
| 7 | -9 |
| 8 | -8 |
| 10 | -7 |
Sweep
| Position | Prev | Sum Before | Interval | Update | New Sum |
|---|---|---|---|---|---|
| 1 | 1 | 0 | none | +9 | 9 |
| 6 | 1 | 9 | [1,6,9] | +15 | 24 |
| 7 | 6 | 24 | [6,7,24] | -9 | 15 |
| 8 | 7 | 15 | [7,8,15] | -8 | 7 |
| 10 | 8 | 7 | [8,10,7] | -7 | 0 |
Final answer:
[[1,6,9],[6,7,24],[7,8,15],[8,10,7]]
Example 3
Input:
segments = [[1,4,5],[1,4,7],[4,7,1],[4,7,11]]
Difference Map
| Coordinate | Net Change |
|---|---|
| 1 | +12 |
| 4 | -12 +12 |
| 7 | -12 |
Simplified:
| Coordinate | Net Change |
|---|---|
| 1 | +12 |
| 4 | 0 |
| 7 | -12 |
Sweep
| Position | Prev | Sum Before | Interval | Update | New Sum |
|---|---|---|---|---|---|
| 1 | 1 | 0 | none | +12 | 12 |
| 4 | 1 | 12 | [1,4,12] | 0 | 12 |
| 7 | 4 | 12 | [4,7,12] | -12 | 0 |
Final answer:
[[1,4,12],[4,7,12]]
Even though the sums are both 12, the intervals must remain separate because the underlying color sets differ.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n log n) | Sorting all event coordinates dominates the runtime |
| Space | O(n) | The difference map stores at most 2n coordinates |
Each segment contributes exactly two events, one start event and one end event.
If there are n segments, there are at most 2n distinct coordinates. Sorting these coordinates requires O(n log n) time.
The sweep itself is linear in the number of event points, so it does not change the overall complexity.
Test Cases
from typing import List
from collections import defaultdict
class Solution:
def splitPainting(self, segments: List[List[int]]) -> List[List[int]]:
diff = defaultdict(int)
for start, end, color in segments:
diff[start] += color
diff[end] -= color
positions = sorted(diff.keys())
result = []
current_sum = 0
prev = positions[0]
for pos in positions:
if current_sum > 0 and prev < pos:
result.append([prev, pos, current_sum])
current_sum += diff[pos]
prev = pos
return result
sol = Solution()
# Example 1
assert sol.splitPainting(
[[1,4,5],[4,7,7],[1,7,9]]
) == [[1,4,14],[4,7,16]]
# Example 2
assert sol.splitPainting(
[[1,7,9],[6,8,15],[8,10,7]]
) == [[1,6,9],[6,7,24],[7,8,15],[8,10,7]]
# Example 3
assert sol.splitPainting(
[[1,4,5],[1,4,7],[4,7,1],[4,7,11]]
) == [[1,4,12],[4,7,12]]
# Single segment
assert sol.splitPainting(
[[2,5,10]]
) == [[2,5,10]]
# Completely overlapping segments
assert sol.splitPainting(
[[1,5,3],[1,5,7]]
) == [[1,5,10]]
# Nested overlap
assert sol.splitPainting(
[[1,10,5],[3,7,8]]
) == [[1,3,5],[3,7,13],[7,10,5]]
# Touching but non-overlapping segments
assert sol.splitPainting(
[[1,3,4],[3,5,6]]
) == [[1,3,4],[3,5,6]]
# Multiple changes at same coordinate
assert sol.splitPainting(
[[1,4,5],[4,6,7],[4,8,9]]
) == [[1,4,5],[4,6,16],[6,8,9]]
# Large color values
assert sol.splitPainting(
[[1,3,10**9],[2,4,10**9]]
) == [[1,2,10**9],[2,3,2*10**9],[3,4,10**9]]
# Complex overlap chain
assert sol.splitPainting(
[[1,5,1],[2,6,2],[3,7,3]]
) == [
[1,2,1],
[2,3,3],
[3,5,6],
[5,6,5],
[6,7,3]
]
| Test | Why |
|---|---|
| Example 1 | Basic overlap handling |
| Example 2 | Partial overlaps and independent regions |
| Example 3 | Same sum but different intervals |
| Single segment | Simplest valid input |
| Completely overlapping segments | Entire interval shares same mixed color |
| Nested overlap | One segment fully inside another |
| Touching segments | Verifies half-closed interval behavior |
| Multiple changes at same coordinate | Ensures updates are combined correctly |
| Large color values | Verifies large integer handling |
| Complex overlap chain | Stress test with multiple simultaneous overlaps |
Edge Cases
One important edge case occurs when multiple segments start or end at the same coordinate. A buggy implementation might process these events separately and accidentally create zero-length intervals or incorrect transitions. The difference-map approach naturally combines all updates at the same coordinate before continuing the sweep, which guarantees correctness.
Another important case is touching intervals such as [1,3) and [3,5). Since intervals are half-closed, these do not overlap. A naive implementation might incorrectly merge them. The sweep-line method handles this correctly because one color is removed at coordinate 3 before the next interval begins.
A third tricky case happens when two adjacent intervals have the same numeric sum but different underlying color sets. For example:
[1,4) -> {5,7}
[4,7) -> {1,11}
Both sums equal 12, but they represent different color mixtures. The algorithm still keeps them separate because interval boundaries are determined by event coordinates, not by whether neighboring sums happen to match.
Another subtle case is unpainted regions. After all active colors disappear, the current sum becomes zero. The implementation explicitly skips intervals with current_sum == 0, ensuring that blank regions are not included in the result.