Skip to content

Commit 14bea44

Browse files
committed
Add new binary search solutions
1 parent b2543d1 commit 14bea44

17 files changed

+1070
-46
lines changed

leetcode/1011.capacity-to-ship-packages-within-d-days.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ class Solution {
6868
}
6969

7070
private fun getShipDays(weights: IntArray, capacity: Int): Int {
71-
var days = 1
71+
var days = 1 // We count the last group since we don't execute if statement in the last iteration. 最後一個分組一定不會執行 if 語句,但是還是要算一天。
7272
var load = 0
7373
// in the order given by weights as problem description
7474
for (w in weights) {

leetcode/1146.snapshot-array.md

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# [1146. Snapshot Array](https://leetcode.com/problems/snapshot-array/description/)
2+
3+
The shapshot array is a data structure that supports the following operations:
4+
```js
5+
nums = [0, 0, 0, 0, 0]
6+
| | |
7+
set 1 3 5
8+
| | |
9+
[1, 3, 5, 0, 0]
10+
| | [1, 3, 5, 0, 0] // snapshot 0
11+
| |
12+
set 7 8
13+
| |
14+
[7, 3, 8, 0, 0]
15+
[1, 3, 7, 8, 0] // snapshot 1
16+
// No update
17+
[1, 3, 7, 8, 0] // snapshot 2
18+
| |
19+
set 9 2
20+
| |
21+
[9, 3, 8, 2, 0]
22+
[9, 3, 8, 2, 0] // snapshot 3
23+
```
24+
Please note that
25+
1. We can set multiple times at the same index, but only one snapshot. Or
26+
2. We can snap multiple times without changing the array. (The value was not updated)
27+
28+
## Brute Force (MLE)
29+
We just save all snapshots. It wastes memory if we have a large array but only a few elements are modified.
30+
31+
```kotlin
32+
class SnapshotArray(private val length: Int) {
33+
34+
private var currentSnapId = 0
35+
36+
private val array = IntArray(length)
37+
private val snapshot = mutableListOf<IntArray>()
38+
39+
fun set(index: Int, `val`: Int) {
40+
array[index] = `val`
41+
}
42+
43+
fun snap(): Int {
44+
snapshot.add(array.clone())
45+
return currentSnapId++
46+
}
47+
48+
fun get(index: Int, snap_id: Int): Int {
49+
return snapshot[snap_id][index]
50+
}
51+
}
52+
```
53+
54+
## Binary Search
55+
The history of the array is saved in the snapshot. Instead of saving all snapshots, we can save only the changes.
56+
57+
```js
58+
nums = [0, 0, 0, 0, 0]
59+
| | |
60+
set 1 3 5
61+
| | |
62+
[1, 3, 5, 0, 0] // snapshot 0
63+
| |
64+
set 7 8
65+
| |
66+
[7, 3, 8, 0, 0] // snapshot 1
67+
// No update
68+
[7, 3, 8, 0, 0] // snapshot 2
69+
| |
70+
set 9 2
71+
| |
72+
[9, 3, 8, 2, 0] // snapshot 3
73+
```
74+
75+
We only record the modified elements when `set()` is called.
76+
```js
77+
// For nums[0]
78+
0 -> 1 -> snapshot 0 -> 7 -> snapshot 1 -> snapshot 2 -> 9 -> snapshot 3
79+
0 --------------------> 1 -------------> 7 ------------> 7 ----------> 9
80+
81+
0 1 3
82+
|--------- 1 -----------|--------------- 7 --------------|----- 9 -----|
83+
84+
// Add modification records for nums[0]
85+
record(snap_id=0, 1)
86+
record(snap_id=1, 7)
87+
record(snap_id=3, 9)
88+
89+
// The corresponding get() for nums[0] at different snapshots
90+
get(snap_id=0) = 1
91+
get(snap_id=1) = 7
92+
get(snap_id=2) = 7 // No update
93+
get(snap_id=3) = 9
94+
95+
```
96+
When we call `get()`, we can binary search the snapshot to find the closest snapshot, we're looking for **the last snapshot <= `snap_id`**.
97+
98+
```kotlin
99+
data class Item(
100+
val snapId: Int,
101+
val value: Int
102+
)
103+
104+
class SnapshotArray(private val length: Int) {
105+
106+
private var currentSnapId = 0
107+
// Space Complexity is `O(n + k)` where `n` is the length of the array and `k` is the number of modifications.
108+
private val modificationRecords = Array<MutableList<Item>>(length) { mutableListOf() }
109+
110+
// Time Complexity is `O(1)`
111+
fun set(index: Int, `val`: Int) {
112+
modificationRecords[index].add(Item(currentSnapId, `val`))
113+
}
114+
115+
// Time Complexity is `O(1)`
116+
fun snap(): Int {
117+
return currentSnapId++
118+
}
119+
120+
// Time Complexity is `O(log(k))` where `k` is the number of modifications.
121+
fun get(index: Int, snap_id: Int): Int {
122+
return binarySearch(modificationRecords[index], snap_id)
123+
}
124+
125+
private fun binarySearch(records: List<Item>, snapId: Int): Int {
126+
var left = 0
127+
var right = records.size - 1
128+
while (left <= right) {
129+
val middle = left + (right - left) / 2
130+
if (records[middle].snapId < snapId) {
131+
left = middle + 1
132+
} else if (records[middle].snapId > snapId) {
133+
right = middle - 1
134+
} else if (records[middle].snapId == snapId) {
135+
left = middle + 1
136+
}
137+
}
138+
return if (right in 0 until records.size) records[right].value else 0
139+
}
140+
}
141+
```
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# [1443. Minimum Time to Collect All Apples in a Tree](https://leetcode.com/problems/minimum-time-to-collect-all-apples-in-a-tree/description/)
2+
3+
## DFS
4+
```js
5+
// 1. 2. 3. 4.
6+
X O X O
7+
/ \ / \ / \ / \
8+
X X X X O X O X
9+
10+
1. No any apple
11+
2. Apple at root
12+
3. Apple at child
13+
4. Apple at root and child
14+
```
15+
16+
> Solved by tree (but need to build tree from edges) or graph approach (to mark visited)
17+
18+
```kotlin
19+
fun minTime(n: Int, edges: Array<IntArray>, hasApple: List<Boolean>): Int {
20+
val tree = buildTree(edges)
21+
return dfs(tree, 0, hasApple, 0, BooleanArray(n))
22+
}
23+
24+
private fun buildTree(edges: Array<IntArray>): HashMap<Int, HashSet<Int>> {
25+
val tree = HashMap<Int, HashSet<Int>>()
26+
for (e in edges) {
27+
// a -> b
28+
val a = e[0]
29+
val b = e[1]
30+
if (!tree.containsKey(a)) tree[a] = HashSet<Int>()
31+
if (!tree.containsKey(b)) tree[b] = HashSet<Int>()
32+
tree[a]!!.add(b)
33+
tree[b]!!.add(a)
34+
}
35+
return tree
36+
}
37+
38+
private fun dfs(
39+
tree: HashMap<Int, HashSet<Int>>,
40+
root: Int,
41+
hasApple: List<Boolean>,
42+
distance: Int,
43+
visited: BooleanArray
44+
): Int {
45+
if (visited[root]) return 0
46+
visited[root] = true
47+
var childrenDistance = 0
48+
49+
tree[root]?.forEach { child ->
50+
childrenDistance += dfs(tree, child, hasApple, 2, visited)
51+
}
52+
if (childrenDistance == 0 && hasApple[root] == false) return 0
53+
else return childrenDistance + distance
54+
}
55+
}
56+
```
57+
58+
* From GPT, different approach from above.
59+
```python
60+
def minTime(n, edges, hasApple):
61+
from collections import defaultdict
62+
63+
# Build the graph
64+
graph = defaultdict(list)
65+
for u, v in edges:
66+
graph[u].append(v)
67+
graph[v].append(u)
68+
69+
def dfs(node, parent):
70+
total_time = 0
71+
for neighbor in graph[node]:
72+
if neighbor == parent:
73+
continue
74+
time = dfs(neighbor, node)
75+
if time > 0 or hasApple[neighbor]:
76+
total_time += time + 2
77+
return total_time
78+
79+
return dfs(0, -1)
80+
```
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# [1482. Minimum Number of Days to Make m Bouquets](https://leetcode.com/problems/minimum-number-of-days-to-make-m-bouquets/)
2+
3+
## Binary Search
4+
For this problem, the minimum number of days to wait is the minimum value in the array, and the maximum number of days to wait is the maximum value in the array.
5+
6+
```js
7+
// m = 1, k = 3 or m = 3, k = 1
8+
days [1, 5, 3] can make?
9+
1 [O, X, X] X
10+
2 [O, X, X] X
11+
3 [O, X, O] X
12+
4 [O, X, O] X
13+
5 [O, O, O] O <- answer
14+
6 [O, O, O] O
15+
...
16+
17+
days [7, 7, 9]
18+
1 [X, X, X] X
19+
2 [X, X, X] X
20+
...
21+
7 [O, O, X] X
22+
8 [O, O, X] X
23+
9 [O, O, O] O <- answer
24+
10 [O, O, O] O
25+
...
26+
```
27+
28+
And if we can make it when waiting for `d` days, then we can also make it when waiting for `d+1`, `d+2`, ..., `max(days)` days. If we can't make it when waiting for `d` days, then we can't make it when waiting for `d-1`, `d-2`, ..., `min(days)` days. This is monotonicity, so we can use binary search to find the answer.
29+
30+
```kotlin
31+
fun minDays(bloomDay: IntArray, m: Int, k: Int): Int {
32+
val n = bloomDay.size
33+
if (n < m * k) return -1
34+
35+
var min = Int.MAX_VALUE
36+
var max = Int.MIN_VALUE
37+
for (b in bloomDay) {
38+
min = minOf(min, b)
39+
max = maxOf(max, b)
40+
}
41+
42+
var left = min
43+
var right = max
44+
while (left <= right) {
45+
val middle = left + (right - left) / 2
46+
47+
if (canMake(bloomDay, middle, m, k)) {
48+
right = middle - 1
49+
} else {
50+
left = middle + 1
51+
}
52+
}
53+
// Remember to check if left is out of bound. That is the case
54+
// [X, X, X]
55+
// ^ left
56+
return if (left > max) -1 else left
57+
}
58+
59+
private fun canMake(bloomDay: IntArray, day: Int, m: Int, k: Int): Boolean {
60+
var numOfBouquets = 0
61+
var count = 0
62+
for (i in 0 until bloomDay.size) {
63+
if (bloomDay[i] <= day) {
64+
count++
65+
} else {
66+
count = 0
67+
}
68+
69+
if (count == k) {
70+
numOfBouquets++
71+
count = 0
72+
}
73+
}
74+
return m <= numOfBouquets
75+
}
76+
77+
// Or equivalently
78+
private fun canMake(bloomDay: IntArray, day: Int, m: Int, k: Int): Boolean {
79+
var i = 0
80+
var numOfBouquets = 0 // If we can make `m` bouquets
81+
while (i < bloomDay.size) {
82+
val canBloom = bloomDay[i] <= day
83+
if (!canBloom) {
84+
i++
85+
continue
86+
}
87+
88+
var j = i
89+
var count = 0 // If we can use `k` flowers to make a bouquet
90+
// If we can bloom, and not enough flowers to make the current bouquet
91+
while (j < bloomDay.size && count < k && bloomDay[j] <= day) {
92+
count++
93+
j++
94+
}
95+
if (k <= count) {
96+
numOfBouquets++
97+
}
98+
i = j
99+
}
100+
return m <= numOfBouquets
101+
}
102+
```
103+
104+
* **Time Complexity:** `O(log(max(days) - min(days)) * n)`
105+
* **Space Complexity:** `O(1)`

0 commit comments

Comments
 (0)