Longest increasing subsequence
algorithm, language-agnostic, python
Solution
I just stumbled in this problem, and came up with this Python 3 implementation:
def subsequence(seq):
if not seq:
return seq
M = [None] * len(seq) # offset by 1 (j -> j-1)
P = [None] * len(seq)
# Since we have at least one element in our list, we can start by
# knowing that the there's at least an increasing subsequence of length one:
# the first element.
L = 1
M[0] = 0
# Looping over the sequence starting from the second element
for i in range(1, len(seq)):
# Binary search: we want the largest j <= L
# such that seq[M[j]] < seq[i] (default j = 0),
# hence we want the lower bound at the end of the search process.
lower = 0
upper = L
# Since the binary search will not look at the upper bound value,
# we'll have to check that manually
if seq[M[upper-1]] < seq[i]:
j = upper
else:
# actual binary search loop
while upper - lower > 1:
mid = (upper + lower) // 2
if seq[M[mid-1]] < seq[i]:
lower = mid
else:
upper = mid
j = lower # this will also set the default value to 0
P[i] = M[j-1]
if j == L or seq[i] < seq[M[j]]:
M[j] = i
L = max(L, j+1)
# Building the result: [seq[M[L-1]], seq[P[M[L-1]]], seq[P[P[M[L-1]]]], ...]
result = []
pos = M[L-1]
for _ in range(L):
result.append(seq[pos])
pos = P[pos]
return result[::-1] # reversing
Since it took me some time to understand how the algorithm works I was a little verbose with comments, and I'll also add a quick explanation:
- `seq` is the input sequence.
- `L` is a number: it gets updated while looping over the sequence and it marks the length of longest incresing subsequence found up to that moment.
- `M` is a list. `M[j-1]` will point to an index of `seq` that holds the smallest value that could be used (at the end) to build an increasing subsequence of length `j`.
- `P` is a list. `P[i]` will point to `M[j]`, where `i` is the index of `seq`. In a few words, it tells which is the previous element of the subsequence. `P` is used to build the result at the end.
How the algorithm works:
- Handle the special case of an empty sequence.
- Start with a subsequence of 1 element.
- Loop over the input sequence with index `i`.
- With a binary search find the `j` that let `seq[M[j]` be `<` than `seq[i]`.
- Update `P`, `M` and `L`.
- Traceback the result and return it reversed.
Note: The only differences with the wikipedia algorithm are the offset of 1 in the `M` list, and that `X` is here called `seq`. I also test it with a slightly improved unit test version of the one showed in Eric Gustavson answer and it passed all tests.
Example:
seq = [30, 10, 20, 50, 40, 80, 60]
0 1 2 3 4 5 6 <-- indexes
At the end we'll have:
M = [1, 2, 4, 6, None, None, None]
P = [None, None, 1, 2, 2, 4, 4]
result = [10, 20, 40, 60]
As you'll see `P` is pretty straightforward. We have to look at it from the end, so it tells that before `60` there's `40,`before `80` there's `40`, before `40` there's `20`, before `50` there's `20` and before `20` there's `10`, stop.
The complicated part is on `M`. At the beginning `M` was `[0, None, None, ...]` since the last element of the subsequence of length 1 (hence position 0 in `M`) was at the index 0: `30`.
At this point we'll start looping on `seq` and look at `10`, since `10` is `<` than `30`, `M` will be updated:
if j == L or seq[i] < seq[M[j]]:
M[j] = i
So now `M` looks like: `[1, None, None, ...]`. This is a good thing, because `10` have more chanches to create a longer increasing subsequence. (The new 1 is the index of 10)
Now it's the turn of `20`. With `10` and `20` we have subsequence of length 2 (index 1 in `M`), so `M` will be: `[1, 2, None, ...]`. (The new 2 is the index of 20)
Now it's the turn of `50`. `50` will not be part of any subsequence so nothing changes.
Now it's the turn of `40`. With `10`, `20` and `40` we have a sub of length 3 (index 2 in `M`, so `M` will be: `[1, 2, 4, None, ...]` . (The new 4 is the index of 40)
And so on...
For a complete walk through the code you can copy and paste it here :)
Problem
Given an input sequence, what is the best way to find the longest (not necessarily continuous) increasing subsequence ``` [0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15] # input [1, 9, 13, 15] # an example of an increasing subsequence (not the longest) [0, 2, 6, 9, 13, 15] # longest increasing subsequence (not a unique answer) [0, 2, 6, 9, 11, 15] # another possible solution ``` I'm looking for the best algorithm. If there is code, Python would be nice, but anything is alright.