Skip to content

Commit 12aeaf2

Browse files
committed
Add and update some notes and problem solving solutions
1 parent d1e415a commit 12aeaf2

37 files changed

+864
-283
lines changed

leetcode/1.two-sum.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ def twoSum(self, nums: List[int], target: int) -> List[int]:
1717
```kotlin
1818
fun twoSum(nums: IntArray, target: Int): IntArray {
1919
val results = IntArray(2)
20+
// value, index
2021
val hashTable = hashMapOf<Int, Int>()
2122
for (i in 0 until nums.size) {
2223
val num = nums[i]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#[1011. Capacity To Ship Packages Within D Days](https://leetcode.com/problems/capacity-to-ship-packages-within-d-days/)

leetcode/128.longest-consecutive-sequence.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,12 @@ fun longestConsecutive(nums: IntArray): Int {
9696
// If we have num - 1 which is consecutive to num, answer will check at num - 1 iteration, not current iteration.
9797
if (!hashSet.contains(num - 1)) {
9898
var next = num + 1
99+
var length = 1
99100
while (hashSet.contains(next)) {
100101
next++
102+
length++
101103
}
102-
maxStreak = max(maxStreak, next - num)
104+
maxStreak = max(maxStreak, length)
103105
}
104106
}
105107
return maxStreak

leetcode/1351.count-negative-numbers-in-a-sorted-matrix.md

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,22 @@ def countNegatives(self, grid: List[List[int]]) -> int:
1616
* **Time Complexity**: `O(m * n)`.
1717
* **Space Complexity**: `O(1)`.
1818

19-
## Count in Z-shape
19+
## Count in Z-Shape
2020
Since each row (column) is sorted, we can count the negative numbers in a Z-shape.
2121

22-
```
23-
++++++
24-
++++--
25-
++++--
26-
+++---
27-
+-----
28-
+-----
22+
```js
23+
X X X O
24+
X O O O
25+
O O O O
26+
27+
// All are negative numbers
28+
O O O
29+
O O O
30+
31+
// All are positive numbers
32+
X X X
33+
X X X
34+
2935
```
3036

3137
![](../media/240.search-a-2d-matrix-ii.png)
@@ -40,6 +46,9 @@ def countNegatives(self, grid: List[List[int]]) -> int:
4046
r = m - 1
4147
c = 0
4248
count = 0
49+
# We don't check if r < 0, becase r will become -1 but we still need to count the following columns. See the example below (all numbers are negative):
50+
# -1, -1, -1
51+
# -1, -1, -1
4352
while c < n:
4453
while 0 <= r and grid[r][c] < 0:
4554
r -= 1
@@ -74,7 +83,6 @@ fun countNegatives(grid: Array<IntArray>): Int {
7483
var count = 0
7584
for (i in 0 until grid.size) {
7685
val index = binarySearch(grid[i])
77-
println(index)
7886
count += grid[i].size - index
7987
}
8088
return count

leetcode/139.word-break.md

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,24 @@
55
// For word "cars" and dictionary ["car", "ca", "rs"]
66
f(car) =
77
f(ca) + "r" in dict ||
8-
f(ca) =
9-
f(c) + "a" in dict ||
10-
f() + "ca" in dict
118
f(c) + "ar" in dict ||
12-
f(c) =
13-
f() + "c" in dict
149
f() + "car" in dict
10+
11+
f(ca) =
12+
f(c) + "a" in dict ||
13+
f() + "ca" in dict
14+
15+
f(c) =
16+
f() + "c" in dict
1517
```
1618

1719
1. **Optimal substructure**: We can break down the string into smaller substrings, and we can build the substrings from dictionary to build the solution for entire string.
1820
2. **Overlapping subproblems**: When breaking down the string, we might have same substrings (`f(c)` from above example), and we can reuse the solution.
1921
3. **Memoization**: We keep track of whether substrings can be built from dictionary.
2022

21-
Suppose we break down the string into `s[0:i]` and `s[i:]`, if we can build `s[0:i]` from dictionary recursively and `s[i:]` exists in dictionary, then we can build `s[0:i] + s[i:]` from dictionary. Then we break down `s[0:i]` into `s[0:j]` and `s[j:i]`, and if we can build `s[0:j]` from dictionary recursively and `s[j:i]` exists in dictionary, then we can build `s[0:j] + s[j:i]` from dictionary. And so on. Therefore, we can solve this problem by dynamic programming.
23+
Suppose we break down the string into `s[0:i]` and `s[i:]`, if we can build `s[0:i]` from dictionary recursively and `s[i:]` exists in dictionary, then we can build `s[0:i] + s[i:]` from dictionary.
24+
25+
Then we break down the subproblem of `s[0:i]`: `s[0:i] = s[0:j]` and `s[j:i]`, and if we can build `s[0:j]` from dictionary recursively and `s[j:i]` exists in dictionary, then we can build `s[0:j] + s[j:i]` from dictionary. And so on. Therefore, we can solve this problem by dynamic programming.
2226

2327
```js
2428
// f(s) = word break function of input string s.
@@ -77,20 +81,51 @@ private fun breakdown(s: String, dict: HashSet<String>, memo: HashMap<String, Bo
7781
In bottom-up approach, We build the solution from smaller substrings to the entire string. Let's define `dp[i]` as the state if we can build the substring `s[0:i]` from dictionary, and the state transition will be:
7882

7983
```js
80-
// s = 0.....j.....i
84+
// s = [0.....j.....i]
85+
// [0... j]
86+
// s[j:i]
8187
// The state of s[0:i] = s[0:j] + s[j:i]
8288
dp[i] =
8389
dp[j] // The state of s[0:j],
8490
&& s[j:i] // The remaining substring, check if it in the dictionary
91+
92+
// We iterate j from 0 to i to build the dp[i]
93+
dp[i] =
94+
dp[0] && s[0:i] in dict ||
95+
dp[1] && s[1:i] in dict ||
96+
dp[2] && s[2:i] in dict ||
97+
...
98+
dp[i - 1] && s[i - 1:i] in dict
8599
```
86-
We will iterate `i` from 0 to `len` for find if we can build substring `s[0:i]`.
87100

88-
For example, `"cars"` and `["car", "ca", "rs"]`:
101+
We will iterate `i` from 0 to `len` to build the `dp[i]` table from bottom up, and for each `i`, we iterate `j` from 0 to `i` to build the `dp[i]` from `dp[j]` and `s[j:i]` in dictionary.
102+
```js
103+
// i = 0, empty string
104+
dp[0] = true // Base case, empty string is in dictionary
105+
106+
// i = 1, s[0:1]
107+
dp[1] = dp[0] && s[0:1] in dict
89108

90-
> The following iterate cooresponds to the nested for loop in the code. `i` is `end` and `j` is `start`.
109+
// i = 2, s[0:2]
110+
dp[2] = dp[0] && s[0:2] in dict ||
111+
dp[1] && s[1:2] in dict
112+
113+
dp[3] = dp[0] && s[0:3] in dict ||
114+
dp[1] && s[1:3] in dict ||
115+
dp[2] && s[2:3] in dict
116+
117+
// So on ...
118+
119+
// i = n, s[0:n]
120+
dp[n] = ... // The final result
121+
```
122+
123+
For example, `"cars"` and `["car", "ca", "rs"]`:
91124

92125
```js
93126
[suffix]
127+
dp[0] = true
128+
94129
j i c a r s
95130
// dp[1] if we can build "c" from dictionary
96131
0, 1, c // dp[0] && dict.contains("c")
@@ -113,19 +148,16 @@ j i c a r s
113148

114149
```kotlin
115150
fun wordBreak(s: String, wordDict: List<String>): Boolean {
116-
val wordDictSet = HashSet<String>(wordDict)
151+
val dict = HashSet<String>(wordDict)
117152
// We define dp size as s.length + 1 because the base case is empty string, that is s[0:0] = "".
118153
val dp = BooleanArray(s.length + 1)
119154
// Base case is empty string, which is true.
120155
dp[0] = true
121-
for (end in 1..s.length) {
122-
for (start in 0..end) {
123-
// s[start:end] = s[0:start] + s[start:end]
124-
// dp[end] = dp[start] && s[start:end] in dict
125-
val suffix = s.substring(start, end)
126-
if (dp[start] && wordDictSet.contains(suffix)) {
127-
dp[end] = true
128-
break
156+
for (i in 1..s.length) {
157+
// dp[i] = dp[0:j] + s[j:i] in dict
158+
for (j in 0..i) {
159+
if (dp[j] && dict.contains(s.substring(j, i))) {
160+
dp[i] = true
129161
}
130162
}
131163
}

leetcode/153.find-minimum-in-rotated-sorted-array.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,18 +52,20 @@ fun findMin(nums: IntArray): Int {
5252
while (left <= right) {
5353
val middle = left + (right - left) / 2
5454
min = minOf(min, nums[middle])
55-
if (nums[middle] > nums[right]) {
55+
56+
// We can list all possible cases
57+
if (nums[left] <= nums[middle] && nums[middle] <= nums[right]) { // O / O: L
58+
right = middle - 1
59+
} else if (nums[left] <= nums[middle] && nums[middle] > nums[right]) { // O / X: R
5660
left = middle + 1
57-
} else {
61+
} else if (nums[left] > nums[middle] && nums[middle] <= nums[right]) { // X / O: L
5862
right = middle - 1
5963
}
6064

61-
// Or equivalently we can list all possible cases
62-
// if (nums[left] <= nums[middle] && nums[middle] <= nums[right]) { // O / O: L
63-
// right = middle - 1
64-
// } else if (nums[left] <= nums[middle] && nums[middle] > nums[right]) { // O / X: R
65+
// Or equivalent: if the right part is not sorted, which is the pivot part, the minimum must be in the right part.
66+
// if (nums[middle] > nums[right]) {
6567
// left = middle + 1
66-
// } else if (nums[left] > nums[middle] && nums[middle] <= nums[right]) { // X / O: L
68+
// } else {
6769
// right = middle - 1
6870
// }
6971
}

leetcode/1642.furthest-building-you-can-reach.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,8 @@ fun furthestBuilding(heights: IntArray, bricks: Int, ladders: Int): Int {
6060
// But if we don't have enough ladder, then try to go back to change
6161
// the smallest height difference to use the remaining brick.
6262
if (ladders < minHeap.size) {
63-
// We have enough bricks
64-
if (minHeap.isNotEmpty() && minHeap.peek() <= remainingBricks) {
65-
remainingBricks -= minHeap.poll()
66-
} else {
63+
remainingBricks -= minHeap.poll()
64+
if (remainingBricks < 0) {
6765
// If we don't have enough bricks, then the "previous" position is the furthest we can reach.
6866
// Minus 1 because we check if we can reach the "next" building in each iteration.
6967
// It failed at the current iteration means that the furtherest we can reach is the previous building.
@@ -76,5 +74,5 @@ fun furthestBuilding(heights: IntArray, bricks: Int, ladders: Int): Int {
7674
}
7775
```
7876

79-
* **Time Complexity**: `O(n lg k)`, `k` is the number of ladders.
80-
* **Space Complexity**: `O(k)`
77+
* **Time Complexity**: `O(n lg L)`, `L` is the number of ladders.
78+
* **Space Complexity**: `O(L)`

leetcode/169.majority-element.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,10 @@ fun majorityElement(nums: IntArray): Int {
4444
if (count == 0) {
4545
majority = nums[i]
4646
}
47-
if (nums[i] != majority) {
48-
count--
49-
} else {
47+
if (nums[i] == majority) {
5048
count++
49+
} else {
50+
count--
5151
}
5252
}
5353
return majority
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# [1870. Minimum Speed to Arrive on Time](https://leetcode.com/problems/minimum-speed-to-arrive-on-time/description/)
2+
3+
## Linear Search
4+
We're finding the minimum speed to reach the office on time. Let's start from speed `1`, and check if it's possible to reach the office within `hour` hours. If not, we increase the speed by 1 and check again.
5+
6+
```js
7+
speed = distance / time
8+
time = distance / speed
9+
10+
s = 1
11+
[1, 3, 2]
12+
ceil(1/1) + ceil(3/1) + 2/1 = 6 <= 6
13+
14+
s = 2
15+
ceil(1/2) + ceil(3/2) + 2/2 = 1 + 2 + 1 = 4
16+
17+
s = 3
18+
ceil(1/3) + ceil(3/3) + 2/3 = 1 + 1 + 0.66 = 2.66 <= 6
19+
```
20+
21+
```kotlin
22+
fun minSpeedOnTime(dist: IntArray, hour: Double): Int {
23+
var speed = 1
24+
while (speed <= 10000000) {
25+
// See below for the implementation of getTotalHours()
26+
if (getTotalHours(dist, speed) <= hour) return speed
27+
speed++
28+
}
29+
return -1
30+
}
31+
```
32+
33+
## Binary Search
34+
We can optimize the linear search solution with some modifications based on some key observations:
35+
1. The minimum speed is `1`, and the maximum speed is the maximum of possible value, which is `10^7`.
36+
2. As we increase the speed, the time to reach the office is shorter, and vice versa. This exhibits the **monotonicity** characteristic, so we can use binary search to find the minimum speed: **We're looking for the first element that satisfies the condition: `getTotalHours(dist, middle) <= hour`**.
37+
38+
```kotlin
39+
fun minSpeedOnTime(dist: IntArray, hour: Double): Int {
40+
val min = 1
41+
val max = 10000000
42+
43+
var left = min
44+
var right = max
45+
while (left <= right) {
46+
val middle = left + (right - left) / 2
47+
val isValid = getTotalHours(dist, middle) <= hour
48+
49+
// Find the first element that meets the condition (can reach the office)
50+
if (isValid) {
51+
right = middle - 1
52+
} else {
53+
left = middle + 1
54+
}
55+
}
56+
// We check if the left is in the range, if not, return -1
57+
return if (left in min..max) left else -1
58+
}
59+
60+
// We calculate the total hours to reach the office with the given speed.
61+
// ceil(dist[0] / speed) + ceil(dist[1] / speed) + ... + ceil(dist[n - 2] / speed) + dist[n - 1] / speed
62+
// The last distance doesn't need to wait, so we don't need to ceil it.
63+
private fun getTotalHours(dist: IntArray, speed: Int): Double {
64+
var hours = 0.0
65+
// We iterate all distances except the last one: because we have to wait if the hour is not an integer.
66+
for (d in 0 until dist.size - 1) {
67+
hours += Math.ceil(dist[d].toDouble() / speed) // If the hour is 1.5, then total hours is 2.
68+
}
69+
// We add the last distance to the total hours, which we don't have to wait.
70+
hours += dist[dist.size - 1].toDouble() / speed
71+
return hours
72+
}
73+
```
74+
75+
* **Time Complexity:** `O(n * log m)`, where `n` is the size of the `dist` and `m` is the maximum speed.
76+
* **Space Complexity:** `O(1)`.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# [2064. Minimized Maximum of Products Distributed to Any Store](https://leetcode.com/problems/minimized-maximum-of-products-distributed-to-any-store/description/)
2+
3+
```kotlin
4+
5+
```

0 commit comments

Comments
 (0)