Skip to content

Commit 630de32

Browse files
committed
Add new heap problems
1 parent 1ba3cf5 commit 630de32

12 files changed

+737
-17
lines changed

leetcode/1405.longest-happy-string.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# [1405. Longest Happy String](https://leetcode.com/problems/longest-happy-string/description/)
2+
3+
## Heap (Greedy)
4+
This is the follow-up question of [984. String Without AAA or BBB](../leetcode/984.string-without-aaa-or-bbb.md), we can use the same idea to solve this:
5+
1. If the count of first > second: "first * 2 second" (like "aab")
6+
2. If the count is the same: "first, second" (like "ab")
7+
8+
```kotlin
9+
fun longestDiverseString(a: Int, b: Int, c: Int): String {
10+
val count = intArrayOf(a, b, c)
11+
val maxHeap = PriorityQueue<Int> { i1, i2 ->
12+
val c1 = count[i1]
13+
val c2 = count[i2]
14+
if (c1 == c2) i1 - i2
15+
else c2 - c1
16+
}
17+
(0..2).forEach {
18+
if (count[it] > 0) maxHeap.add(it)
19+
}
20+
val str = StringBuilder()
21+
while (maxHeap.size >= 2) {
22+
val i1 = maxHeap.poll()
23+
val i2 = maxHeap.poll()
24+
val char1 = 'a' + i1
25+
var char2 = 'a' + i2
26+
27+
// first > second: "first * 2 second"
28+
if (count[i1] > count[i2]) {
29+
str.append("${char1}${char1}${char2}")
30+
count[i1] -= 2
31+
count[i2] -= 1
32+
} else { // "first, second"
33+
str.append("${char1s}${char2}")
34+
count[i1] -= 1
35+
count[i2] -= 1
36+
}
37+
if (count[i1] > 0) maxHeap.add(i1)
38+
if (count[i2] > 0) maxHeap.add(i2)
39+
}
40+
// Append the last one character at most 2 times
41+
if (maxHeap.isNotEmpty()) {
42+
val i1 = maxHeap.poll()
43+
val c1 = min(count[i1], 2)
44+
str.append(('a' + i1).toString().repeat(c1))
45+
}
46+
return str.toString()
47+
}
48+
```

leetcode/1834.single-threaded-cpu.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# [1834. Single-Threaded CPU](https://leetcode.com/problems/single-threaded-cpu/description/)
2+
3+
## Simulation + Heap
4+
We can simulate the time elapse by starting the time from 0 and increment, we can sort the tasks by enqueue time so that we can enqueue task to available tasks for CPU processing as time elapses. **But before we sort the tasks, we need to keep the original index of the task so that we can return the order of the tasks.**
5+
6+
If CPU is idle, and the available tasks is empty (no task to process now), we can advance the current time to the next task's enqueue time. (B, and C as example)
7+
```js
8+
time:
9+
t---->t
10+
1, 2, 3, 4, 5, 6, 7, 8, 9, 10
11+
A----->
12+
B-------->
13+
C----->
14+
D-->
15+
```
16+
17+
If the all the tasks which enqueue time <= current time, we can add those tasks to the available tasks for CPU processing. (B, C as example) We can't add `D` because its enqueue time is 9, and the current time is 6.
18+
```js
19+
time:
20+
t
21+
1, 2, 3, 4, 5, 6, 7, 8, 9, 10
22+
...
23+
B-------->
24+
C----->
25+
D-->
26+
```
27+
28+
After adding tasks to the available tasks, we can process the task with the smallest processing time first, we can use a min heap to store the available tasks to find the task with the smallest processing time. We process the task by polling task from the min heap, advance the current time by the processing time of that task, and add the original index of the task to the order list.
29+
30+
We can keep adding tasks to the available tasks or process the task with the smallest processing time until all the tasks are processed.
31+
32+
```kotlin
33+
data class Task(
34+
val enqueueTime: Int,
35+
val processingTime: Int,
36+
val originalIndex: Int
37+
)
38+
39+
fun getOrder(originalTasks: Array<IntArray>): IntArray {
40+
// We build the tasks with original index
41+
val tasks = originalTasks.mapIndexed { index, task ->
42+
Task(task[0], task[1], index)
43+
}.toTypedArray()
44+
45+
val n = tasks.size
46+
val order = mutableListOf<Int>()
47+
val availableTasks = PriorityQueue<Task>() { t1, t2 ->
48+
if (t1.processingTime == t2.processingTime) {
49+
t1.originalIndex - t2.originalIndex
50+
} else {
51+
t1.processingTime - t2.processingTime
52+
}
53+
}
54+
tasks.sortBy { it.enqueueTime }
55+
56+
var i = 0
57+
var currentTime = 0
58+
while (i < n || availableTasks.isNotEmpty()) {
59+
// We add tasks to available tasks if the enqueue time <= current time
60+
while (i < n && tasks[i].enqueueTime <= currentTime) {
61+
availableTasks.add(tasks[i])
62+
i++
63+
}
64+
65+
// If CPU is idle, and no task to process now, we can advance the current time to the next task's enqueue time
66+
if (i < n && availableTasks.isEmpty()) {
67+
currentTime = tasks[i].enqueueTime
68+
continue
69+
}
70+
71+
// We process the task with the smallest processing time first, we can update
72+
// the process order and advance the current time
73+
if (availableTasks.isNotEmpty()) {
74+
val processTask = availableTasks.poll()
75+
order.add(processTask.originalIndex)
76+
currentTime += processTask.processingTime
77+
}
78+
}
79+
return order.toIntArray()
80+
}
81+
```
82+
83+
* **Time Complexity:** `O(n log n)`, where `n` is the number of tasks. We need to sort the tasks by enqueue time, and we need to add tasks to the available tasks for CPU processing, which takes O(logN) time.
84+
* **Space Complexity:** `O(n)`, where `n` is the number of tasks. We need to store the tasks, available tasks, and order list.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# [1845. Seat Reservation Manager](https://leetcode.com/problems/seat-reservation-manager/submissions/)
2+
3+
## Heap
4+
We initailze a priority queue with all the seats. When we reserve a seat, we poll the top element from the queue. When we unreserve a seat, we add the seat number back to the queue.
5+
```kotlin
6+
class SeatManager(private val n: Int) {
7+
8+
private val seats = PriorityQueue<Int>()
9+
10+
init {
11+
(1..n).forEach {
12+
seats.add(it)
13+
}
14+
}
15+
16+
fun reserve(): Int {
17+
return seats.poll()
18+
}
19+
20+
fun unreserve(seatNumber: Int) {
21+
seats.add(seatNumber)
22+
}
23+
24+
}
25+
```
26+
27+
* **Time Complexity:** `O(log n)` for reserve and unreserve operations, and `O(n)` for initialization, total is `O(n log n)`.
28+
* **Space Complexity:** `O(n)` for priority queue.
29+
30+
Another solution is to use a marker variable, if we don't unreverse the seat, then the smallest-numbered unreserved seat is always monotonic increasing. We can use a variable to store the smallest unreserved seat number, and increase it by 1 when we reserve a seat.
31+
32+
It guarantees that we invoke `unreserve(x)` after `reserve()`, so `x < marker`. We use a min heap to maintain the unreserved seats, similar idea as [2336. Smallest Number in Infinite Set](../leetcode/2336.smallest-number-in-infinite-set.md).
33+
34+
```js
35+
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...
36+
|---> marker
37+
|--heap--|
38+
```
39+
40+
```kotlin
41+
class SeatManager(private val n: Int) {
42+
43+
private var marker = 1
44+
private val unreservedSeats = PriorityQueue<Int>()
45+
46+
fun reserve(): Int {
47+
if (unreservedSeats.isEmpty()) {
48+
return marker++
49+
}
50+
return unreservedSeats.poll()
51+
}
52+
53+
fun unreserve(seatNumber: Int) {
54+
unreservedSeats.add(seatNumber)
55+
}
56+
}
57+
```
58+
59+
* **Time Complexity:** `O(log n)` for reserve and unreserve operations, and `O(1)` for initialization, total is `O(n log n)`.
60+
* **Space Complexity:** `O(q)` for priority queue.
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# [2336. Smallest Number in Infinite Set](https://leetcode.com/problems/smallest-number-in-infinite-set/description/)
2+
3+
## Test Cases
4+
### Edge / Corner Cases
5+
```js
6+
1, 2, 3, 4, 5
7+
X X X X O // pop 4 times
8+
9+
1, 2, 3, 4, 5
10+
X O O X O // add 2, 3 back
11+
12+
pop() = 2
13+
pop() = 3
14+
pop() = 5 // It's 5, not 4
15+
```
16+
17+
```js
18+
add(2) // add the number that is still in the set
19+
pop() = 1
20+
```
21+
22+
## Heap + Set (or `TreeSet`)
23+
We can add all possible numbers to the set as the initial set, and use a minimum heap to find the smallest number in the set, including the numbers that are added back. But this approach is not efficient, because the set could be infinite.
24+
25+
How can we solve this problem efficiently? Let's break down the problem:
26+
* If we don't add number back, we can just use a variable to store the current smallest number in the infinite set.
27+
* If we support adding numbers back, we need a data structure that can find the smallest number dynamically, which is heap. And we need a set to avoid duplicate adding. So if we add the number back, we can add it to the heap and set.
28+
29+
```js
30+
1, 2, 3, 4, 5, 6, ...
31+
X X X X |
32+
| -----> // handle by `num`
33+
num
34+
<------|
35+
// handle by heap and set
36+
```
37+
38+
To summarize to our approach:
39+
* `popSmallest()`: we check if heap is empty, if not, there are some numbers added back, we just pop the smallest number from the heap. If the heap is empty, we return the current smallest number in the infinite set, that is maintained by a variable.
40+
* `addBack(number)`: we check if the number is still in the infinite set, if not, we add it to the heap and set.
41+
42+
```js
43+
1, 2, 3, 4, 5, 6, ...
44+
X X X X |
45+
|
46+
num
47+
add(6) // num < 6: It's still in the infinite set
48+
49+
add(1) // add 1 to heap and set
50+
pop() = 1
51+
pop() = 5
52+
```
53+
54+
```kotlin
55+
class SmallestInfiniteSet() {
56+
// The smallest number in the infinite set.
57+
private var currentSmallest = 1
58+
59+
// The heap and set to store the numbers that are added back.
60+
private val minHeap = PriorityQueue<Int>()
61+
private val set = HashSet<Int>()
62+
63+
fun popSmallest(): Int {
64+
// If we have added some numbers back, we just pop the smallest number from the heap.
65+
if (set.isNotEmpty()) {
66+
val value = minHeap.poll()
67+
set.remove(value)
68+
return value
69+
} else {
70+
// Otherwise, we just return the current smallest number in the infinite set.
71+
return currentSmallest++
72+
}
73+
}
74+
75+
fun addBack(number: Int) {
76+
if (currentSmallest <= number || // If we add the number that is still in the infinite set.
77+
set.contains(number)) { // Or we add the number that is already added back.
78+
return
79+
}
80+
81+
set.add(number)
82+
minHeap.add(number)
83+
}
84+
}
85+
86+
// Or equivalently using TreeSet
87+
class SmallestInfiniteSet() {
88+
private val set = TreeSet<Int>() // To store the numbers that are removed and added back
89+
private var currentSmallest = 1
90+
91+
fun popSmallest(): Int {
92+
if (set.isNotEmpty()) {
93+
val value = set.pollFirst()
94+
return value
95+
} else {
96+
return currentSmallest++
97+
}
98+
}
99+
100+
fun addBack(number: Int) {
101+
if (currentSmallest <= number || set.contains(number)) return
102+
set.add(number)
103+
}
104+
}
105+
```
106+
107+
* **Time Complexity:** `O(log n)` for `popSmallest()`, `O(log n)` for `addBack()`
108+
* **Space Complexity:** `O(n)`

0 commit comments

Comments
 (0)