Skip to content

Commit 19ef31f

Browse files
committed
Annotate the essential and review problems for sliding window and two pointers, update the notes for the sliding window and two pointers problems
1 parent 67b585f commit 19ef31f

29 files changed

+708
-282
lines changed

leetcode/1089.duplicate-zeros.md

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# [1089. Duplicate Zeros](https://leetcode.com/problems/duplicate-zeros/)
22

3-
## Two Pointers
3+
## Two Pointers (Extra Space)
44
We just copy the element from the original array to the new array, and if we found the zero, we duplicate it.
55
```kotlin
66
fun duplicateZeros(arr: IntArray) {
@@ -43,6 +43,11 @@ fun duplicateZeros(arr: IntArray): Unit {
4343
var write = n - 1 + zeros
4444
for (read in n - 1 downTo 0) {
4545
if (arr[read] == 0) {
46+
// Pitfall: We need to check one by one, not just write -= 2, see below.
47+
// if (write >= n) {
48+
// // Wrong, we will skip checking at the index `write - 1`
49+
// write -= 2
50+
// }
4651
repeat(2) {
4752
if (write >= n) {
4853
write--
@@ -53,13 +58,25 @@ fun duplicateZeros(arr: IntArray): Unit {
5358
} else {
5459
if (write >= n) {
5560
write--
56-
continue
61+
} else {
62+
arr[write] = arr[read]
63+
write--
5764
}
58-
arr[write--] = arr[read]
5965
}
6066
}
6167
}
6268
```
6369

6470
* **Time Complexity**: `O(n)`.
65-
* **Space Complexity**: `O(1)`, it's just in-place copy, no extra space required.
71+
* **Space Complexity**: `O(1)`, it's just in-place copy, no extra space required.
72+
73+
The pitfall I made is that I didn't check the index `write` one by one, so I missed the checking at the index `write - 1`:
74+
75+
```js
76+
n-1, n, n+1
77+
..., X], 0
78+
r
79+
w-2 w
80+
```
81+
82+
If we just `write -= 2` when `arr[read] == 0` and `write` is at index `n` (out of bounds), we will miss updateing the element `X` at the index `n - 1`. Check the example `[8,4,5,0,0,0,0,7]`.

leetcode/1234.replace-the-substring-for-balanced-string.md

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,41 @@
11
# [1234. Replace the Substring for Balanced String](https://leetcode.com/problems/replace-the-substring-for-balanced-string/description/)
22

3+
## Test Cases
4+
### Edge / Corner Cases
5+
* The string is already balanced.
6+
* The string contains only single type of characters.
7+
38
## Binary Search + Sliding Window
4-
We're looking for a substring (window) which we can replace to make the string balanced.
9+
We're looking for a substring (window) which we can replace any characters within the window to make the string balanced.
510
```js
611
QQQQWWWWWEEERRR
7-
... |-----| ...
12+
^^^^|-----|^^^^
13+
// We focus on the outside of the window, we can replace the chars in the window to make the string balanced.
814
```
915

10-
**Idea!!** For a given window, we should count the chars outside the window, and if there is any missing chars, we can always replace the chars in the window to make the string balanced, that is a valid window.
16+
**Idea!!** For a given window, we should count the chars outside the window, and if there is any missing chars (`count[char] <= n / 4`), we can always replace the chars in the window to make the string balanced, that is a valid window.
1117

12-
If there are some redundant chars, it's impossible to replace in the window, the window is invalid.
18+
If there are chars that is more than `n / 4`, it's impossible to replace in the window, the window is invalid.
1319

14-
Based on this observation, the window chould be range in `0 .. n-1`, we can check if the window is valid (all the counts of each chars <= `n/4`) or not (any of the count of char > `n/4`). It's a fixed search space and monotonic, we can use binary search to find the smallest window.
20+
Based on this observation, the window chould be range in `0 .. n - 1`, we can check if the window is valid (all the counts of each chars <= `n / 4`) or not. As the window size increases, the outside of the window is smaller, it's more likely to satisfy the condition. (all the counts of each chars <= `n / 4`), and vice versa. Since it's a fixed search space and monotonic, we can use binary search to find the smallest window.
21+
22+
```js
23+
window = 0 1 2 3 4 ... n - 1
24+
valid = X X X O O ... O
25+
^ // The smallest window
26+
```
1527

1628
---
1729
```markdown
1830
我們要找某個最小區間,這區間之內可以補足任意的字母,使得整個字串變平衡。
1931

20-
那麼我們可以看區間外面的字母,少哪一個我都可以在這區間補足。
32+
那麼我們可以**看區間外面的字母,少哪一個我都可以在這區間補足。**
2133
然而,如果區間外面的某個字母太多了,那麼在這區間也不能補足或減少,這區間就變成無效。
2234

23-
所以回到題目,長度 n 的字串,我們定一個區間,我們要看區間「外」的所有字母是否都 <= `n/4`:
24-
* `count[x] < n/4`: 可以在區間補齊。
25-
* `count[x] = n/4`: 合法,也不做任何事。
26-
* `count[x] > n/4`: 區間也不能修正,此區間無效。
35+
所以回到題目,長度 n 的字串,我們定一個區間,我們要看區間「外」的所有字母是否都 <= `n / 4`:
36+
* `count[x] < n / 4`: 區間外有少,可以在區間補齊。
37+
* `count[x] = n / 4`: 合法,也不做任何事。
38+
* `count[x] > n/4`: 區間內也不能修正,此區間無效。
2739

2840
因為區間的長度範圍在 0 ~ n,加上這樣的思路,我們可以先找到這合法的區間大小是多少(還不用是最小區間),我們可以二分搜尋,判斷這區間大小是否合法。
2941
```
@@ -38,7 +50,7 @@ fun balancedString(s: String): Int {
3850
counts[index]++
3951
}
4052
var left = 0
41-
var right = s.length - 1
53+
var right = s.length
4254
while (left <= right) {
4355
val middle = left + (right - left) / 2
4456
if (check(s, middle, counts.clone())) {
@@ -97,7 +109,7 @@ fun balancedString(s: String): Int {
97109
var left = 0
98110
var right = 0
99111
var result = n
100-
while (right < n) {
112+
for (right in 0 until n) {
101113
val rightIndex = charsIndex[s[right]]!!
102114
counts[rightIndex]--
103115
while (isValid(n, counts)) {
@@ -110,7 +122,6 @@ fun balancedString(s: String): Int {
110122
// Or we can check if left is out of bound here
111123
// if (left >= n) break
112124
}
113-
right++
114125
}
115126
return result
116127
}

leetcode/1423.maximum-points-you-can-obtain-from-cards.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ We can take one card from left and right, but we don't know which side is better
55

66
```js
77
[2,7,8,...,5,1,6], k = 3
8-
X _,_,_ // (0,k)
9-
_ _,_ // (1,k-1)
10-
_,_ _ // (2,k-2)
11-
_,_,_ X // (3,k-3)
8+
.........,O,O,O // (0,k)
9+
O,.........,O,O // (1,k-1)
10+
O,O,.........,O // (2,k-2)
11+
O,O,O,......... // (3,k-3)
1212
// Find the maximum among them
1313
```
1414

@@ -27,7 +27,9 @@ O O O |---| // sum(0..4) = total - sum(1..5)
2727

2828
Then this is a fixed size sliding window problem:
2929

30-
> Or it finds the minimum of `n - k` slinding window.
30+
* Window: The sum of subarray size `n - k`.
31+
32+
> Or it finds the minimum of `n - k` sliding window.
3133
3234
```kotlin
3335
fun maxScore(cardPoints: IntArray, k: Int): Int {
@@ -38,7 +40,6 @@ fun maxScore(cardPoints: IntArray, k: Int): Int {
3840
}
3941
var sum = 0
4042
var maxScore = 0
41-
// We will subtract the sum of the window from the total sum
4243
val windowSize = n - k
4344
for (i in cardPoints.indices) {
4445
sum += cardPoints[i]

leetcode/1574.shortest-subarray-to-be-removed-to-make-array-sorted.md

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,33 @@
11
# [1574. Shortest Subarray to be Removed to Make Array Sorted](https://leetcode.com/problems/shortest-subarray-to-be-removed-to-make-array-sorted/description/)
22

3+
## Test Cases
4+
### Edge / Corner Cases
5+
* The array is already sorted. (Important to check this condition)
6+
```
7+
Input: [1, 2, 3]
8+
Output: 0
9+
```
10+
* The array is in descending order.
11+
```
12+
Input: [3, 2, 1]
13+
Output: 2
14+
```
15+
316
## Prework
417
For a given array, we can breakdown into three parts:
518
```js
619
[X X X X] [Y Y Y Y] [Z Z Z Z]
720
0 i j n-1
821
```
22+
23+
To make the whole array sorted by removing `Y` part (may be empty), we have to satisfy the following conditions:
924
1. `[0:i]` is non-decreasing.
1025
2. `[j:n-1]` is non-decreasing.
1126
3. `arr[i] <= arr[j]`.
1227

13-
For the example input `[1, 2, 3, 9, 4, 2, 3, 5]`, we can find the two non-decreasing subarrays (for 1. and 2. above):
14-
* Find the longest non-decreasing subarray from the beginning.
15-
* Find the longest non-decreasing subarray from the end.
28+
So the first steps is to identify the longest non-decreasing subarrays from the beginning and the end of the array. (for the conditions 1. and 2. above)
29+
30+
For the example input `[1, 2, 3, 9, 4, 2, 3, 5]`, we can identify the two non-decreasing subarrays from the beginning and the end of the array:
1631

1732
```js
1833
[1, 2, 3, 9, 4, 2, 3, 5]
@@ -24,7 +39,7 @@ For the example input `[1, 2, 3, 9, 4, 2, 3, 5]`, we can find the two non-decrea
2439
R
2540
```
2641

27-
After finding the longest non-decreasing subarray from the beginning and the end, there are 3 options to consider:
42+
After identifying the longest non-decreasing subarray from the beginning and the end, there are 3 options to consider as the result:
2843
1. We remove the left part: `[X, X, X, X, X, 2, 3, 5]`
2944
2. We remove the right part: `[1, 2, 3, 9, X, X, X, X]`
3045
3. We remove the middle part and merge the two non-decreasing subarrays into one: `[1, 2, 3, 9, 2, 3, 5]`
@@ -39,17 +54,17 @@ fun findLengthOfShortestSubarray(arr: IntArray): Int {
3954
// Find the longest non-decreasing subarray from the beginning
4055
while (left + 1 < n && arr[left] <= arr[left + 1]) left++
4156

42-
// All are non-decreasing
57+
// All are non-decreasing (it's necessary to check this condition)
4358
if (left == n - 1) return 0
4459

4560
// Find the longest non-decreasing subarray from the end
4661
var right = n - 1
47-
while (0 < right - 1 && arr[right - 1] <= arr[right]) right--
62+
while (0 <= right - 1 && arr[right - 1] <= arr[right]) right--
4863

4964
// Remove left part or right part
5065
var result = minOf(n - left - 1, right)
5166

52-
// TODO: See below...
67+
// TODO: Not finished, see below...
5368
}
5469
```
5570

@@ -64,34 +79,39 @@ How to move the pointers `L` and `R`?
6479
> Very nice illustration to explain how to move the pointers `L` and `R`: https://leetcode.cn/problems/shortest-subarray-to-be-removed-to-make-array-sorted/solutions/2189149/dong-hua-yi-xie-jiu-cuo-liang-chong-xie-iijwz/
6580
6681
## Two Pointers + Binary Search
67-
The first approach is to iterate all possible `l` in `0..L`, then find the first `r` in `R..n-1` that satisfies `arr[l] <= arr[r]`:
82+
Since the `R..n-1` is non-decreasing (sorted), so we can iterate all possible `l` in `0..L`, then binary search the first `r` in `R..n-1` that satisfies `arr[l] <= arr[r]` (keep sorted after merge):
83+
84+
```js
85+
[0, ... L], ..., [R, ... n-1]
86+
l -> r ->
87+
```
6888

6989
```js
7090
0 1 2 3 4 5 6 7 // index
7191
1, 2, 3, 9, 2, 3, 5 // value
7292
L R // the original non-decreasing subarrays
93+
|--------| |--------|
7394
l r // l = 0 -> r = 4, to remove is 3 (2, 3, 9)
7495
l r // l = 1 -> r = 4, to remove is 2 (3, 9)
7596
l r // l = 2 -> r = 5, to remove is 1 (9, 2)
7697
l r // r is out of range, to remove is 3 (2, 3, 5)
7798
```
7899

79-
Because `R..n-1` are non-decreasing (sorted), we can use binary search to find the first `r` that satisfies `arr[l] <= arr[r]`, then update the minimum result.
80-
81100
```kotlin
82101
fun findLengthOfShortestSubarray(arr: IntArray): Int {
83102
// ... see above
84103

85104
for (l in 0..left) {
86-
val r = search(arr, arr[l], right, n - 1)
87-
result = minOf(result, r - l - 1)
105+
// right is monotonic increasing, so we don't start from original right index
106+
right = search(arr, arr[l], right)
107+
result = minOf(result, right - l - 1)
88108
}
89109
return result
90110
}
91111

92-
private fun search(arr: IntArray, target: Int, start: Int, end: Int): Int {
93-
var left = start
94-
var right = end
112+
private fun search(arr: IntArray, target: Int, startIndex: Int): Int {
113+
var left = startIndex
114+
var right = arr.size - 1
95115
while (left <= right) {
96116
val middle = left + (right - left) / 2
97117
if (target <= arr[middle]) {
@@ -108,13 +128,14 @@ private fun search(arr: IntArray, target: Int, start: Int, end: Int): Int {
108128
* **Space Complexity:** `O(1)`
109129

110130
## Two Pointers
111-
We can optimize the above solution based on the same idea by using two pointers approach.
131+
We can optimize the above solution based on the same idea (to find the first `r` that satisfies `arr[l] <= arr[r]`) by using two pointers approach.
112132

113133
Because `0..L` and `R..n-1` are both non-decreasing, as we find the first `r` that satisfies `arr[l] <= arr[r]` in the first iteration, then we can start from the previous `r` to find the next `r` that satisfies `arr[l] <= arr[r]`, we don't need to start from `R` again.
114134

115135
```js
116136
1, 3, 8, 9, 2, 3, 5, 6, 7, 8
117137
L R
138+
|--------| |--------------|
118139
l r1 // first iteration
119140
l ->r2 // second iteration, r starts from the previous r
120141
l r2 -------> r3 // third iteration
@@ -158,7 +179,7 @@ fun findLengthOfShortestSubarray(arr: IntArray): Int {
158179
if (left == n - 1) return 0
159180

160181
var right = n - 1
161-
while (0 < right - 1 && arr[right - 1] <= arr[right]) right--
182+
while (0 <= right - 1 && arr[right - 1] <= arr[right]) right--
162183

163184
var result = Int.MAX_VALUE
164185
val originalRight = right

leetcode/1695.maximum-erasure-value.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,31 @@ fun maximumUniqueSubarray(nums: IntArray): Int {
4444
}
4545
return maxScore
4646
}
47+
```
48+
49+
## WA
50+
I was finding **the longest unique element,** which may not lead to maximum sum.
51+
```kotlin
52+
fun maximumUniqueSubarray(nums: IntArray): Int {
53+
val set = HashSet<Int>()
54+
var left = 0
55+
var maxScore = 0
56+
var l = 0
57+
var r = 0
58+
for (right in nums.indices) {
59+
while (set.contains(nums[right])) {
60+
set.remove(nums[left])
61+
left++
62+
}
63+
set.add(nums[right])
64+
if (right - left >= r - l) {
65+
l = left
66+
r = right
67+
}
68+
}
69+
for (i in l..r) {
70+
maxScore += nums[i]
71+
}
72+
return maxScore
73+
}
4774
```

leetcode/1775.equal-sum-arrays-with-minimum-number-of-operations.md

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
11
# [1775. Equal Sum Arrays With Minimum Number of Operations](https://leetcode.com/problems/equal-sum-arrays-with-minimum-number-of-operations/description/)
22

3-
## Greedy
4-
**Idea!** To make the two sum's equal with the minimum nubmer of operation, we need either to increase to `6` in the smaller sum array or decrease to `1` in the larger sum array. If we change all elements to `6` and its sum is still less than another array or change all elements to `1` and its sum is still larger than another array, it's impossible to make the two sums equal.
3+
## Greedy + Two Pointers
4+
**Idea!** To make the two sum's equal with the minimum nubmer of operation, we should maximize the increase of the smaller sum array or minimize the decrease of the larger sum array.
5+
6+
We need either to increase to `6` in the smaller sum array or decrease to `1` in the larger sum array greedily. If we change all elements to `6` and its sum is still less than another array or change all elements to `1` and its sum is still larger than another array, it's impossible to make the two sums equal.
57

68
> Intuitions: We want to minimize the number of operations. So we pick 1 from the smaller array and change it to 6. Or, we pick 6 from the larger array and change it to 1. That's the fastest way to converge two sums.
79
>
810
> 最少操作数的本质是:和小的数组里面每个数尽量变为 6;和大的数组里面的每个数尽量缩小为 1
911
10-
Based on the above idea and suppose `sum(A) > sum(B)`, we can change the largest element in `A` to `1` to decrease `sum(A)`, or change the smallest number in `B` to `6` to increase `sum(B)` (if we still have number to chnage). We can repeat this process until `sum(A) <= sum(B)` or we have no number to change.
12+
Based on the above idea and suppose `sum(A) > sum(B)`, we can change the largest element in `A` to `1` to decrease `sum(A)`, or change the smallest number in `B` to `6` to increase `sum(B)` (if we still have numbers to change). We can repeat this process until `sum(A) <= sum(B)` or we have no number to change (return `-1`).
1113

1214
```js
1315
-------|--------------------|--------
14-
sum(B) sum(A)
15-
--> // Increase sum(B) by changing the smallest number in B to 6
16-
<-- // Decrease sum(A) by changing the largest number in A to 1
17-
|<- ->|
18-
sum(A) <= sum(B) // Until sum(A) <= sum(B) or we have no number to change
16+
sum(B) < sum(A)
17+
-->| // Increase sum(B) by changing the smallest number in B to 6
18+
|<-- // Decrease sum(A) by changing the largest number in A to 1
19+
sum(A) <= sum(B) // Until sum(A) <= sum(B) or we have no number to change
20+
|<-- -->|
1921
```
2022

2123
Our greedy stragety is to change the largest number in `A` to `1` or the smallest number in `B` to `6` at each step. And we also choose the larger difference of change between `A` and `B` to change or either if they are equal.
@@ -36,10 +38,7 @@ B = [1, ...]
3638

3739
We repeat this process until `sum(A) <= sum(B)` or we have no number to change. The number of operations is the number of steps we take.
3840

39-
Why `sum(A) < sum(B)` works? Because we can always change the largest number in `A` to `1` or the smallest number in `B` to `6` to make `sum(A) <= sum(B)`, when `sum(A)` becomes less than `sum(B)`, we can increase or decrease some numbers that we have changed before to make `sum(A) == sum(B)`.
40-
41-
> If target diff < 0, we can always make target diff == 0 by reverting some numbers from 6 to the less number or 1 to greater number.
42-
41+
Why `sum(A) < sum(B)` is valid? It's over-adjusted, we can revert some numbers that we have changed before to make `sum(A) == sum(B)`. If target diff < 0, we can always make target diff == 0 by reverting some numbers from 6 to the less number or 1 to greater number, see the following example:
4342

4443
```js
4544
A = [6, 6] = 12
@@ -97,7 +96,7 @@ fun minOperations(nums1: IntArray, nums2: IntArray): Int {
9796
} else if (p2 < n) { // We still have B, but A is out of bound
9897
sum2 += (6 - nums2[p2])
9998
p2++
100-
} else { // There is no any number to change
99+
} else { // There is no any number to change but the sum1 > sum2
101100
return -1
102101
}
103102
operations++

0 commit comments

Comments
 (0)