2158. Amount of New Area Painted Each Day
There is a long and thin painting that can be represented by a number line.
You are given a 0-indexed 2D integer array paint
of length n
, where paint[i] = [start_i, end_i]
. This means that on the i
th day you need to paint the area between start_i
and end_i
.
Painting the same area multiple times will create an uneven painting so you only want to paint each area of the painting at most once.
Return an integer array worklog
of length n
, where worklog[i]
is the amount of new area that you painted on the i
th day.
Example 1:
Input: paint = [[1,4],[4,7],[5,8]]
Output: [3,3,1]
Explanation:
- On day
0
, paint everything between1
and4
. - The amount of new area painted on day
0
is4 - 1 = 3
. - On day
1
, paint everything between4
and7
. - The amount of new area painted on day
1
is7 - 4 = 3
. - On day
2
, paint everything between7
and8
. - Everything between
5
and7
was already painted on day1
. - The amount of new area painted on day
2
is8 - 7 = 1
.
Example 2:
Input: paint = [[1,4],[5,8],[4,7]]
Output: [3,3,1]
Explanation:
- On day
0
, paint everything between1
and4
. - The amount of new area painted on day
0
is4 - 1 = 3
. - On day
1
, paint everything between5
and8
. - The amount of new area painted on day
1
is8 - 5 = 3
. - On day
2
, paint everything between4
and5
. - Everything between
5
and7
was already painted on day1
. - The amount of new area painted on day 2 is 5 - 4 = 1.
Example 3:
Input: paint = [[1,5],[2,4]]
Output: [4,0]
Explanation:
- On day
0
, paint everything between1
and5
. - The amount of new area painted on day
0
is5 - 1 = 4
. - On day
1
, paint nothing because everything between2
and4
was already painted on day0
. - The amount of new area painted on day
1
is0
.
Constraints:
1 <= paint.length <= 10^5
paint[i].length == 2
0 <= start_i < end_i <= 5 * 10^4
Solution
Naive solution in
Let's split the number line into blocks such that for the th block covers the interval . Create a boolean array to store whether each block has been painted.
On day , we are tasked with painting blocks to . We can check each of these blocks, painting the unpainted ones (we also keep count of how many blocks we paint because that's what the question asks for). In the worst case, we have to check every block on every day. Let be the number of days (up to ) and let be the largest number that appears in the input (up to ). The time complexity is . This is not fast enough.
A simple solution in
Instead of using a boolean array, we can use a BBST (balanced binary search tree) to store the indices of the unpainted blocks. At the start, we insert into the BBST. When we paint a node, we delete its node from the BBST. In our time complexity analysis, it will become clear why we chose to use a BBST.
On each day, we search for the first node . If it's also , we delete it. We repeatedly do this until there are no more blocks between and .
<mark style={{ backgroundColor: "lightblue" }}>The intuition behind this solution is that we don't want need to needlessly loop over painted blocks; as soon as a block is painted, it's no longer useful, so we delete it. Otherwise, in future days, we'd have to keep checking whether each block has been painted. A BBST can do what we need: find and delete single items quickly.
Time complexity
Inserting into the BBST at the start takes time.
Finding the first node and deleting a node both take , and we do them at most and times, respectively.
In total, our algorithm takes .
Space complexity
A BBST of elements takes space.
Built-in BBSTs
Most programming languages have built-in BBSTS so we don't have to code them ourselves. C++ has set
, Java has TreeSet
, Python has SortedList
, and JavaScript has SortedSet
(but it's not supported on LeetCode).
C++ Solution
class Solution {
public:
vector<int> amountPainted(vector<vector<int>>& paint) {
set<int> unpainted;
vector<int> ans(paint.size());
for (int i = 0; i <= 50000; i++) {
unpainted.insert(i);
}
for (int i = 0; i < paint.size(); i++) {
int left = paint[i][0], right = paint[i][1];
// Repeatedly delete the first element >= left until it becomes >= right
// This clears values in [left, right) from the set
for (auto it = unpainted.lower_bound(left); *it < right; it = unpainted.erase(it), ans[i]++);
}
return ans;
}
};
Java Solution
class Solution {
public int[] amountPainted(int[][] paint) {
TreeSet<Integer> unpainted = new TreeSet<>();
int[] ans = new int[paint.length];
for (int i = 0; i <= 50000; i++) {
unpainted.add(i);
}
for (int i = 0; i < paint.length; i++) {
int left = paint[i][0], right = paint[i][1];
// Repeatedly delete the first element >= left until it becomes >= right
// This clears values in [left, right) from the TreeSet
while (true) {
int next = unpainted.ceiling(left);
if (next >= right)
break;
unpainted.remove(next);
ans[i]++;
}
}
return ans;
}
}
Python Solution
from sortedcontainers import SortedList class Solution: def amountPainted(self, paint: List[List[int]]) -> List[int]: unpainted = SortedList([i for i in range(0, 50001)]) ans = [0 for _ in range(len(paint))] for i in range(len(paint)): left, right = paint[i] # Repeatedly delete the first element >= left until it becomes >= right # This clears values in [left, right) from the SortedList while unpainted[ind := unpainted.bisect_left(left)] < right: unpainted.__delitem__(ind) ans[i] += 1 return ans
JavaScript Solution
var SortedSet = require("collections/sorted-set");
/**
* @param {number[][]} paint
* @return {number[]}
*/
var amountPainted = function (paint) {
const n = paint.length;
const ans = new Array(n).fill(0);
const unpainted = new SortedSet(Array.from(Array(50001).keys()));
for (let i = 0; i < n; i++) {
(left = paint[i][0]), (right = paint[i][1]);
// Repeatedly delete the first element >= left until it becomes >= right
// This clears values in [left, right) from the SortedSet
while ((node = unpainted.findLeastGreaterThanOrEqual(left)).value < right) {
unpainted.delete(node.value);
ans[i]++;
}
}
return ans;
};
Alternative solution
Instead of storing the unpainted blocks, we can store the painted segments. We store them as (left, right)
pairs in a BBST, where no segments intersect. Each day, we delete segments fully contained in [left_i, right_i]
, then merge partially overlapping segments with it, all while keeping count of how many blocks we've painted this day. We create, delete, and check for overlaps in segments for a total time complexity of . This solution is trickier to implement—code will not be presented here.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorWhat's the output of running the following function using input [30, 20, 10, 100, 33, 12]
?
1def fun(arr: List[int]) -> List[int]:
2 import heapq
3 heapq.heapify(arr)
4 res = []
5 for i in range(3):
6 res.append(heapq.heappop(arr))
7 return res
8
1public static int[] fun(int[] arr) {
2 int[] res = new int[3];
3 PriorityQueue<Integer> heap = new PriorityQueue<>();
4 for (int i = 0; i < arr.length; i++) {
5 heap.add(arr[i]);
6 }
7 for (int i = 0; i < 3; i++) {
8 res[i] = heap.poll();
9 }
10 return res;
11}
12
1class HeapItem {
2 constructor(item, priority = item) {
3 this.item = item;
4 this.priority = priority;
5 }
6}
7
8class MinHeap {
9 constructor() {
10 this.heap = [];
11 }
12
13 push(node) {
14 // insert the new node at the end of the heap array
15 this.heap.push(node);
16 // find the correct position for the new node
17 this.bubble_up();
18 }
19
20 bubble_up() {
21 let index = this.heap.length - 1;
22
23 while (index > 0) {
24 const element = this.heap[index];
25 const parentIndex = Math.floor((index - 1) / 2);
26 const parent = this.heap[parentIndex];
27
28 if (parent.priority <= element.priority) break;
29 // if the parent is bigger than the child then swap the parent and child
30 this.heap[index] = parent;
31 this.heap[parentIndex] = element;
32 index = parentIndex;
33 }
34 }
35
36 pop() {
37 const min = this.heap[0];
38 this.heap[0] = this.heap[this.size() - 1];
39 this.heap.pop();
40 this.bubble_down();
41 return min;
42 }
43
44 bubble_down() {
45 let index = 0;
46 let min = index;
47 const n = this.heap.length;
48
49 while (index < n) {
50 const left = 2 * index + 1;
51 const right = left + 1;
52
53 if (left < n && this.heap[left].priority < this.heap[min].priority) {
54 min = left;
55 }
56 if (right < n && this.heap[right].priority < this.heap[min].priority) {
57 min = right;
58 }
59 if (min === index) break;
60 [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61 index = min;
62 }
63 }
64
65 peek() {
66 return this.heap[0];
67 }
68
69 size() {
70 return this.heap.length;
71 }
72}
73
74function fun(arr) {
75 const heap = new MinHeap();
76 for (const x of arr) {
77 heap.push(new HeapItem(x));
78 }
79 const res = [];
80 for (let i = 0; i < 3; i++) {
81 res.push(heap.pop().item);
82 }
83 return res;
84}
85
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!