You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: src/sequences/longest_increasing_subsequence.md
+83-29Lines changed: 83 additions & 29 deletions
Original file line number
Diff line number
Diff line change
@@ -27,26 +27,38 @@ First we will search only for the **length** of the longest increasing subsequen
27
27
### Finding the length
28
28
29
29
To accomplish this task, we define an array $d[0 \dots n-1]$, where $d[i]$ is the length of the longest increasing subsequence that ends in the element at index $i$.
30
+
31
+
!!! example
32
+
33
+
$$\begin{array}{ll}
34
+
a &= \{8, 3, 4, 6, 5, 2, 0, 7, 9, 1\} \\
35
+
d &= \{1, 1, 2, 2, 3, 1, 1, 4, 5, 2\}
36
+
\end{array}$$
37
+
38
+
The longest increasing subsequence that ends at index 4 is $\{3, 4, 5\}$ with a length of 3, the longest ending at index 8 is either $\{3, 4, 5, 7, 9\}$ or $\{3, 4, 6, 7, 9\}$, both having length 5, and the longest ending at index 9 is $\{0, 1\}$ having length 2.
39
+
30
40
We will compute this array gradually: first $d[0]$, then $d[1]$, and so on.
31
41
After this array is computed, the answer to the problem will be the maximum value in the array $d[]$.
32
42
33
43
So let the current index be $i$.
34
44
I.e. we want to compute the value $d[i]$ and all previous values $d[0], \dots, d[i-1]$ are already known.
35
45
Then there are two options:
36
46
37
-
- $d[i] = 1$: the required subsequence consists of only the element $a[i]$.
38
-
- $d[i] > 1$: then in the required subsequence is another number before the number $a[i]$.
39
-
Let's focus on that number:
40
-
it can be any element $a[j]$ with $j = 0 \dots i-1$ and $a[j] < a[i]$.
41
-
In this fashion we can compute $d[i]$ using the following formula:
42
-
If we fixate the index $j$, then the longest increasing subsequence ending in the two elements $a[j]$ and $a[i]$ has the length $d[j] + 1$.
43
-
All of these values $d[j]$ are already known, so we can directly compute $d[i]$ with:
47
+
- $d[i] = 1$: the required subsequence consists only of the element $a[i]$.
48
+
49
+
- $d[i] > 1$: The subsequence will end it $a[i]$, and right before it will be some number $a[j]$ with $j < i$ and $a[j] < a[i]$.
50
+
51
+
It's easy to see, that the subsequence ending in $a[j]$ will itself be one of the longest increasing subsequences that ends in $a[j]$.
52
+
The number $a[i]$ just extends that longest increasing subsequence by one number.
53
+
54
+
Therefore, we can just iterate over all $j < i$ with $a[j] < a[i]$, and take the longest sequence that we get by appending $a[i]$ to the longest increasing subsequence ending in $a[j]$.
55
+
The longest increasing subsequence ending in $a[j]$ has length $d[j]$, extending it by one gives the length $d[j] + 1$.
@@ -133,13 +145,47 @@ This method leads to a slightly longer code, but in return we save some memory.
133
145
In order to obtain a faster solution for the problem, we construct a different dynamic programming solution that runs in $O(n^2)$, and then later improve it to $O(n \log n)$.
134
146
135
147
We will use the dynamic programming array $d[0 \dots n]$.
136
-
This time $d[i]$ will be the element at which a subsequence of length $i$ terminates.
137
-
If there are multiple such sequences, then we take the one that ends in the smallest element.
148
+
This time $d[l]$ doesn't corresponds to the element $a[i]$ or to an prefix of the array.
149
+
$d[l]$ will be the smallest element at which an increasing subsequence of length $l$ ends.
138
150
139
-
Initially we assume $d[0] = -\infty$ and for all other elements $d[i] = \infty$.
151
+
Initially we assume $d[0] = -\infty$ and for all other lengths $d[l] = \infty$.
140
152
141
153
We will again gradually process the numbers, first $a[0]$, then $a[1]$, etc, and in each step maintain the array $d[]$ so that it is up to date.
142
154
155
+
!!! example
156
+
157
+
Given the array $a = \{8, 3, 4, 6, 5, 2, 0, 7, 9, 1\}$, here are all their prefixes and their dynamic programming array.
158
+
Notice, that the values of the array don't always change at the end.
159
+
160
+
$$
161
+
\begin{array}{ll}
162
+
\text{prefix} = \{\} &\quad d = \{-\infty, \infty, \dots\}\\
163
+
\text{prefix} = \{8\} &\quad d = \{-\infty, 8, \infty, \dots\}\\
What have the conditions to be, that we write the current number $a[i]$ into the $d[0 \dots n]$ array?
178
+
179
+
We set $d[l] = a[i]$, if there is a longest increasing sequence of length $l$ that ends in $a[i]$, and there is no longest increasing sequence of length $l$ that ends in a smaller number.
180
+
Similar to the previous approach, if we remove the number $a[i]$ from the longest increasing sequence of length $l$, we get another longest increasing sequence of length $l -1$.
181
+
So we want to extend a longest increasing sequence of length $l - 1$ by the number $a[i]$, and obviously the longest increasing sequence of length $l - 1$ that ends with the smallest element will work the best, in other words the sequence of length $l-1$ that ends in element $d[l-1]$.
182
+
183
+
There is a longest increasing sequence of length $l - 1$ that we can extend with the number $a[i]$, exactly if $d[l-1] < a[i]$.
184
+
So we can just iterate over each length $l$, and check if we can extend a longest increasing sequence of length $l - 1$ by checking the criteria.
185
+
186
+
Additionally we also need to check, if we maybe have already found a longest increasing sequence of length $l$ with a smaller number at the end.
187
+
So we only update if $a[i] < d[l]$.
188
+
143
189
After processing all the elements of $a[]$ the length of the desired subsequence is the largest $l$ with $d[l] < \infty$.
144
190
145
191
```{.cpp file=lis_method2_n2}
@@ -150,32 +196,40 @@ int lis(vector<int> const& a) {
150
196
d[0] = -INF;
151
197
152
198
for (int i = 0; i < n; i++) {
153
-
for (int j = 1; j <= n; j++) {
154
-
if (d[j-1] < a[i] && a[i] < d[j])
155
-
d[j] = a[i];
199
+
for (int l = 1; l <= n; l++) {
200
+
if (d[l-1] < a[i] && a[i] < d[l])
201
+
d[l] = a[i];
156
202
}
157
203
}
158
204
159
205
int ans = 0;
160
-
for (int i = 0; i <= n; i++) {
161
-
if (d[i] < INF)
162
-
ans = i;
206
+
for (int l = 0; l <= n; l++) {
207
+
if (d[l] < INF)
208
+
ans = l;
163
209
}
164
210
return ans;
165
211
}
166
212
```
167
213
168
214
We now make two important observations.
169
215
170
-
The array $d$ will always be sorted:
171
-
$d[i-1] \le d[i]$ for all $i = 1 \dots n$.
172
-
And also the element $a[i]$ will only update at most one value $d[j]$.
216
+
1. The array $d$ will always be sorted:
217
+
$d[l-1] < d[l]$ for all $i = 1 \dots n$.
173
218
174
-
Thus we can find this element in the array $d[]$ using binary search in $O(\log n)$.
175
-
In fact we are simply looking in the array $d[]$ for the first number that is strictly greater than $a[i]$, and we try to update this element in the same way as the above implementation.
219
+
This is trivial, as you can just remove the last element from the increasing subsequence of length $l$, and you get a increasing subsequence of length $l-1$ with a smalller ending number.
220
+
221
+
2. The element $a[i]$ will only update at most one value $d[l]$.
222
+
223
+
This follows immediately from the above implementation.
224
+
There can only be one place in the array with $d[l-1] < a[i] < d[l]$.
225
+
226
+
Thus we can find this element in the array $d[]$ using [binary search](../num_methods/binary_search.md) in $O(\log n)$.
227
+
In fact we can simply look in the array $d[]$ for the first number that is strictly greater than $a[i]$, and we try to update this element in the same way as the above implementation.
176
228
177
229
### Implementation
178
230
231
+
This gives us the improved $O(n \log n)$ implementation:
232
+
179
233
```{.cpp file=lis_method2_nlogn}
180
234
int lis(vector<int> const& a) {
181
235
int n = a.size();
@@ -184,15 +238,15 @@ int lis(vector<int> const& a) {
184
238
d[0] = -INF;
185
239
186
240
for (int i = 0; i < n; i++) {
187
-
int j = upper_bound(d.begin(), d.end(), a[i]) - d.begin();
188
-
if (d[j-1] < a[i] && a[i] < d[j])
189
-
d[j] = a[i];
241
+
int l = upper_bound(d.begin(), d.end(), a[i]) - d.begin();
0 commit comments