Skip to content

Commit bbd3122

Browse files
committed
some lis improvements
1 parent 336caf3 commit bbd3122

File tree

1 file changed

+83
-29
lines changed

1 file changed

+83
-29
lines changed

src/sequences/longest_increasing_subsequence.md

Lines changed: 83 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -27,26 +27,38 @@ First we will search only for the **length** of the longest increasing subsequen
2727
### Finding the length
2828

2929
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+
3040
We will compute this array gradually: first $d[0]$, then $d[1]$, and so on.
3141
After this array is computed, the answer to the problem will be the maximum value in the array $d[]$.
3242

3343
So let the current index be $i$.
3444
I.e. we want to compute the value $d[i]$ and all previous values $d[0], \dots, d[i-1]$ are already known.
3545
Then there are two options:
3646

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$.
4456

45-
$$d[i] = \max_{\substack{j = 0 \dots i-1 \\\\ a[j] < a[i]}} \left(d[j] + 1\right)$$
57+
$$d[i] = \max_{\substack{j < i \\\\ a[j] < a[i]}} \left(d[j] + 1\right)$$
4658

4759
If we combine these two cases we get the final answer for $d[i]$:
4860

49-
$$d[i] = \max\left(1, \max_{\substack{j = 0 \dots i-1 \\\\ a[j] < a[i]}} \left(d[j] + 1\right)\right)$$
61+
$$d[i] = \max\left(1, \max_{\substack{j < i \\\\ a[j] < a[i]}} \left(d[j] + 1\right)\right)$$
5062

5163
### Implementation
5264

@@ -133,13 +145,47 @@ This method leads to a slightly longer code, but in return we save some memory.
133145
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)$.
134146

135147
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.
138150

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$.
140152

141153
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.
142154

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\}\\
164+
\text{prefix} = \{8, 3\} &\quad d = \{-\infty, 3, \infty, \dots\}\\
165+
\text{prefix} = \{8, 3, 4\} &\quad d = \{-\infty, 3, 4, \infty, \dots\}\\
166+
\text{prefix} = \{8, 3, 4, 6\} &\quad d = \{-\infty, 3, 4, 6, \infty, \dots\}\\
167+
\text{prefix} = \{8, 3, 4, 6, 5\} &\quad d = \{-\infty, 3, 4, 5, \infty, \dots\}\\
168+
\text{prefix} = \{8, 3, 4, 6, 5, 2\} &\quad d = \{-\infty, 2, 4, 5, \infty, \dots \}\\
169+
\text{prefix} = \{8, 3, 4, 6, 5, 2, 0\} &\quad d = \{-\infty, 0, 4, 5, \infty, \dots \}\\
170+
\text{prefix} = \{8, 3, 4, 6, 5, 2, 0, 7\} &\quad d = \{-\infty, 0, 4, 5, 7, \infty, \dots \}\\
171+
\text{prefix} = \{8, 3, 4, 6, 5, 2, 0, 7, 9\} &\quad d = \{-\infty, 0, 4, 5, 7, 9, \infty, \dots \}\\
172+
\text{prefix} = \{8, 3, 4, 6, 5, 2, 0, 7, 9, 1\} &\quad d = \{-\infty, 0, 1, 5, 7, 9, \infty, \dots \}\\
173+
\end{array}
174+
$$
175+
176+
When we process $a[i]$, we can ask ourselves.
177+
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+
143189
After processing all the elements of $a[]$ the length of the desired subsequence is the largest $l$ with $d[l] < \infty$.
144190

145191
```{.cpp file=lis_method2_n2}
@@ -150,32 +196,40 @@ int lis(vector<int> const& a) {
150196
d[0] = -INF;
151197

152198
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];
156202
}
157203
}
158204

159205
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;
163209
}
164210
return ans;
165211
}
166212
```
167213
168214
We now make two important observations.
169215
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$.
173218
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.
176228
177229
### Implementation
178230
231+
This gives us the improved $O(n \log n)$ implementation:
232+
179233
```{.cpp file=lis_method2_nlogn}
180234
int lis(vector<int> const& a) {
181235
int n = a.size();
@@ -184,15 +238,15 @@ int lis(vector<int> const& a) {
184238
d[0] = -INF;
185239
186240
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();
242+
if (d[l-1] < a[i] && a[i] < d[l])
243+
d[l] = a[i];
190244
}
191245
192246
int ans = 0;
193-
for (int i = 0; i <= n; i++) {
194-
if (d[i] < INF)
195-
ans = i;
247+
for (int l = 0; l <= n; l++) {
248+
if (d[l] < INF)
249+
ans = l;
196250
}
197251
return ans;
198252
}

0 commit comments

Comments
 (0)