Neeldhara
  • About
  • Research
    • Overview
    • People
    • Publications
    • Surveys
  • Teaching
    • Courses
    • Materials
  • Lists
    • Puzzles
    • Bookmarks
  • Exposition
    • Talks
    • Videos
  • Events
  • Blog

1. Longest Increasing Subsequence

  • Advanced Algorithms
    • Flows
    • Intractability
    • Approximation Algorithms
    • Randomized Algorithms
    • Greedy Algorithms
      • 1. Storing Files on a Tape
      • 2. A Scheduling Problem
      • 3. Stable Matchings
    • Matroids
      • 1. The Motivation
      • 2. The Definition
      • 3. Greedy Works!
      • 4. Scheduling with Deadlines
    • Dynamic Programming
      • 1. Longest Increasing Subsequence
      • 2. Subset Sum
      • 3. Set Cover
      • 4. Optimal BSTs
      • 5. Maximum Independent Set on Trees

On this page

  • The Problem
  • The Solution — Take 0 (a dead end)
  • The Solution — Take 1 (building on the bad take)
  • The Solution — Take 2 (a different approach)
  • Time and Space Complexity
  • Correctness

1. Longest Increasing Subsequence

algonotes
lecturenotes
Published

May 16, 2023

Acknowledgements

Some of this material is borrowed from Jeff Erickson’s chapters on Recursion, Backtracking, and Dynamic Programming. In particular, the (very nice) analogy with traffic lights is from Erickson. Check them out for a more detailed comparison between recursive and memoized implementations. Thanks to Arya for pointing out a correction.

The Problem

For any sequence QQQ, a subsequence of QQQ is another sequence obtained from QQQ by deleting zero or more elements, without changing the order of the remaining elements; the elements of the subsequence need not be contiguous in QQQ.

For example, when you drive down a major street in any city, you drive through a sequence of intersections with traffic lights, but you only have to stop at a subsequence of those intersections, where the traffic lights are red. If you’re very lucky, you never stop at all: the empty sequence is a subsequence of QQQ. On the other hand, if you’re very unlucky, you may have to stop at every intersection: QQQ is a subsequence of itself.

Here are some examples:

  1. BOAT is a subsequence of BOX-OF-CHOCOLATES.
  2. OOF is a subsequence of BOX-OF-CHOCOLATES, but OO-F is not.
  3. O-O-OOLA is a subsequence of BOX-OF-CHOCOLATES, but FOX is not.
  4. FOOL and BHOLA are a subsequences of BOX-OF-CHOCOLATES, but LAX and BETA are not.

Now suppose we are given a sequence of integers, and we need to find the length of a longest subsequence whose elements are in increasing order:

  • the input is an integer array A[1..n]A[1 .. n]A[1..n], and
  • we need to compute the length of a longest possible sequence of indices 1⩽i1<i2<⋯<iℓ⩽n1 \leqslant i_1<i_2<\cdots<i_{\ell} \leqslant n1⩽i1​<i2​<⋯<iℓ​⩽n such that A[ik]<A[ik+1]A\left[i_k\right]<A\left[i_{k+1}\right]A[ik​]<A[ik+1​] for all kkk.

We will use LIS to refer to the phrase longest increasing subsequence. Note that there may be multiple increasing subsequences, and even multiple choices for increasing subsequences that are the longest. The approaches below that work correctly to find the length of a LIS are easily adaptable to finding a LIS as well.

The Solution — Take 0 (a dead end)

What are the fragments (AKA, what do we want to store)?

For any 1⩽i⩽n1 \leqslant i \leqslant n1⩽i⩽n, let A[i…]A[i\ldots]A[i…] denote the subarray of AAA starting at A[i]A[i]A[i], and let T[i]T[i]T[i] denote the length of a LIS of A[i…]A[i\ldots]A[i…]. The TTT’s will be our fragments.

Are the fragments going to be useful (AKA, where is the final answer)?

T[1]T[1]T[1] is evidently the answer we are looking for!

Do we have a kickstart (AKA, what are the base cases)?

T[n]=1T[n] = 1T[n]=1, since there is only one increasing subsequence of A[n…]A[n\ldots]A[n…]: no competition.

How do the fragments come together (AKA, how do we compute the values that we have agreed to store)?

Consider the issue of computing the value of T[i]T[i]T[i]. Let SiS_iSi​ be the set of all the longest increasing subsequences of A[i…]A[i\ldots]A[i…]. Let the common length of all the subsequences in SiS_iSi​ be kkk. We want to determine the value of kkk. In the spirit of magic1, let us assume that we know the values of T[j]T[j]T[j] for j>ij > ij>i.

In particular, say we know the value of T[i+1]T[i+1]T[i+1] to be ℓ\ellℓ. Observe that ℓ⩽k⩽ℓ+1\ell \leqslant k \leqslant \ell+1ℓ⩽k⩽ℓ+1:

  • the first inequality follows from the fact that every LIS of A[i+1…]A[i+1\ldots]A[i+1…] is also an increasing subsequence of A[i…]A[i\ldots]A[i…], and
  • the second inequality follows from the fact that if there was a LIS of A[i…]A[i\ldots]A[i…] of length ℓ+2\ell + 2ℓ+2 or more, then the tail of this subsequence (i.e, the subsequence without the first element) would be an increasing subsequence of length at least ℓ+1\ell + 1ℓ+1 of A[i+1…]A[i+1\ldots]A[i+1…], which contradicts T[i+1]=ℓT[i+1] = \ellT[i+1]=ℓ.

So all we need to figure out — for computing T[i]T[i]T[i] — is whether the value of T[i+1]T[i+1]T[i+1] should be incremented or not. How do figure this out?

Well, let Si+1S_{i+1}Si+1​ be the set of all the longest increasing subsequences of A[i+1…]A[i+1\ldots]A[i+1…]. Call a subsequence in Si+1S_{i+1}Si+1​ useful if the first value is bigger than A[i]A[i]A[i], and useless otherwise. Why is this notion of interest? Well, if all subsequences in Si+1S_{i+1}Si+1​ are useless, then we claim that k=ℓk = \ellk=ℓ! Indeed, if k=ℓ+1k = \ell+1k=ℓ+1, then either:

  • there is an increasing subsequence of length ℓ+1\ell + 1ℓ+1 that begins at an index j>ij > ij>i (this is not possible because T[i+1]=ℓT[i+1] = \ellT[i+1]=ℓ),
  • there is an increasing subsequence of length ℓ+1\ell + 1ℓ+1 that begins with A[i]A[i]A[i] (this is not possible because then the tail of this sequence — all elements but A[i]A[i]A[i] — will belong to Si+1S_{i+1}Si+1​ and also be useful, contradicting our assumption that all subsequences in Si+1S_{i+1}Si+1​ are useless).

On the other hand, if even one subsequence in Si+1S_{i+1}Si+1​ is useful, then we know that we can “tag” A[i]A[i]A[i] to it as a prefix and we will have an increasing subsequence in A[i…]A[i\ldots]A[i…] which is in fact the best we can hope for in terms of length: so this is when T[i]=ℓ+1T[i] = \ell + 1T[i]=ℓ+1.

Having said all this, unfortunately, by just staring hard at the value of T[i+1]T[i+1]T[i+1], we have no way of knowing if Si+1S_{i+1}Si+1​ is full of useless subsequences or not. So it is not at all clear how we can determine if the increment should happen or not!

Can we put the pieces together without getting stuck (AKA, are the dependencies in step #4 acyclic)?

Not relevant, since step #2 is unspecified.

The Solution — Take 1 (building on the bad take)

What are the fragments (AKA, what do we want to store)?

We were stuck before because we had no way of knowing if the set of longest increasing subsequences of A[i…]A[i\ldots]A[i…] had a useful one, and useful subsequences were ones that started off with an element larger than A[i]A[i]A[i]. Let’s see if we can upgrade our fragments to incorporate this information explicitly: so given a starting index for a subarray (say jjj) and a target array location (say i<ji < ji<j), let us record the length of a longest increasing subsequence of A[j…]A[j\ldots]A[j…] whose first element is larger than A[i]A[i]A[i].

In notation: for any 1⩽i<j⩽n1 \leqslant i < j \leqslant n1⩽i<j⩽n, let S(i,j)S_{(i,j)}S(i,j)​ denote the set of increasing subsequences of A[j…]A[j\ldots]A[j…] that start with an element larger than A[i]A[i]A[i]. Now, let T[i,j]T[i,j]T[i,j] denote the length of a longest sequence in S(i,j)S_{(i,j)}S(i,j)​. The TTT’s will be our fragments.

Are the fragments going to be useful (AKA, where is the final answer)?

max⁡i∈[n]T[i,i+1]+1\max_{i \in [n]} T[i,i+1] + 1maxi∈[n]​T[i,i+1]+1 is the answer we are looking for: since we don’t know where the first element of an optimal solution is located in an array, so we hazard a guess properly by taking a max⁡\maxmax over all possiblities.

You could alternatively artificially expand the array to n+1n+1n+1 elements, with the last nnn elements being the same as AAA, and the first element being an element that’s smaller than min⁡i∈[n]A[i]\min_{i \in [n]} A[i]mini∈[n]​A[i]: then the final answer is simply T[1,1]T[1,1]T[1,1].

Do we have a kickstart (AKA, what are the base cases)?

For all 1⩽i<n1 \leqslant i < n1⩽i<n, we have:

  • T[i,n]=1T[i,n] = 1T[i,n]=1 if A[n]>A[i]A[n] > A[i]A[n]>A[i].
  • T[i,n]=0T[i,n] = 0T[i,n]=0 if A[n]⩽A[i]A[n] \leqslant A[i]A[n]⩽A[i].

These equalities follow directly from the definition of T[i,j]T[i,j]T[i,j].

How do the fragments come together (AKA, how do we compute the values that we have agreed to store)?

Consider the issue of computing the value of T[i,j]T[i,j]T[i,j] for 1⩽i⩽n1 \leqslant i \leqslant n1⩽i⩽n and i+1⩽j⩽n−1i+1 \leqslant j \leqslant n-1i+1⩽j⩽n−1.

First, suppose A[i]⩾A[j]A[i] \geqslant A[j]A[i]⩾A[j]. Then, if we have a subsequence in the set S(i,j)S_{(i,j)}S(i,j)​, then it certainly does not begin with A[j]A[j]A[j]: since this would be a violation of the criteria for membership in Si,jS_{i,j}Si,j​. Therefore, in this situation, every subsequence that belongs to S(i,j)S_{(i,j)}S(i,j)​ is also a subsequence in S(i,j+1)S_{(i,j+1)}S(i,j+1)​. Therefore, we have: T[i,j]=T[i,j+1]T[i,j] = T[i,j+1]T[i,j]=T[i,j+1].

On the other hand, suppose A[i]<A[j]A[i] < A[j]A[i]<A[j]. Then the subsequences in S(i,j)S_{(i,j)}S(i,j)​ are of two kinds: those that begin with A[j]A[j]A[j] and those that do not. Let us denote these sets by X(i,j)X_{(i,j)}X(i,j)​ and Y(i,j)Y_{(i,j)}Y(i,j)​ respectively. Note that:

  • Any longest increasing subsequence in X(i,j)X_{(i,j)}X(i,j)​ is the element A[j]A[j]A[j] followed by one of the longest increasing subsequences in Sj,j+1S_{j,j+1}Sj,j+1​, and therefore the length of any longest subsequence in X(i,j)X_{(i,j)}X(i,j)​ is captured by T[j,j+1]+1T[j,j+1] + 1T[j,j+1]+1.

  • Any longest increasing subsequence in X(i,j)X_{(i,j)}X(i,j)​ is a longest increasing subsequence in S(i,j+1)S_{(i,j+1)}S(i,j+1)​ (this is, in fact, similar to the previous case), and therefore the length of any longest subsequence in X(i,j)X_{(i,j)}X(i,j)​ is captured by T[i,j+1]T[i,j+1]T[i,j+1].

Now, observing that if we have a set SSS that is partitioned into XXX and YYY, then max⁡(A)=max⁡(max⁡(X),max⁡(Y))\max(A) = \max(\max(X),\max(Y))max(A)=max(max(X),max(Y)), we can summarize our observations above in the following recurrence.

T⁡(i,j)={ T (i,j+1) if A[i]⩾A[j]max⁡{ T (i,j+1)1+ T (j,j+1)} otherwise \operatorname{T}(i, j)= \begin{cases}\text { T }(i, j+1) & \text { if } A[i] \geqslant A[j] \\ \max \left\{\begin{array}{c}\text { T }(i, j+1) \\ 1+\text { T }(j, j+1)\end{array}\right\} & \text { otherwise }\end{cases}T(i,j)=⎩⎨⎧​ T (i,j+1)max{ T (i,j+1)1+ T (j,j+1)​}​ if A[i]⩾A[j] otherwise ​

Can we put the pieces together without getting stuck (AKA, are the dependencies in step #4 acyclic)?

Note that computing T[i,j]T[i,j]T[i,j] involves pinging two entries: T[i,j+1]T[i,j+1]T[i,j+1] and T[j,j+1]T[j,j+1]T[j,j+1]. So as long as all T[⋆,j+1]T[\star,j+1]T[⋆,j+1] entries are computed before we get to T[i,j]T[i,j]T[i,j], then we are covered.

Recall that the base case directly captures all entries of the form T[⋆,n]T[\star,n]T[⋆,n], and one valid sequence of comptuations would involve working our way backwards along the second dimension: i.e, compute T[⋆,n−1],T[⋆,n−2],⋯T[\star,n-1], T[\star,n-2],\cdotsT[⋆,n−1],T[⋆,n−2],⋯, and so on.

Sequence of computations for the recurrence above.

The Solution — Take 2 (a different approach)

What are the fragments (AKA, what do we want to store)?

For the sake of analysis, fix an optimal solution 1⩽i1<i2<⋯<ik⩽n1 \leqslant i_1 < i_2 < \cdots < i_k \leqslant n1⩽i1​<i2​<⋯<ik​⩽n. We don’t know i1i_1i1​: but what if we try to guess i1i_1i1​? That is, go over all possible choices for the starting point: 1,2,…,n1,2,\ldots,n1,2,…,n? Fix such a choice.

At this point, we are asking ourselves the question: what is the length of a longest increasing subsequence in AAA that begins at index iii? If we could answer this question correctly for every index iii, then we are done, because we can simply output the best of all answers obtained.

So let SiS_iSi​ denote the set of all increasing subsequences of AAA that start at A[i]A[i]A[i] and let T[i]T[i]T[i] denote the length of a LIS in SiS_iSi​. The TTT’s will be our fragments.

Are the fragments going to be useful (AKA, where is the final answer)?

As hinted at above, max⁡i∈[n]T[i]\max_{i \in [n]} T[i]maxi∈[n]​T[i] is the answer we are looking for.

Do we have a kickstart (AKA, what are the base cases)?

Clearly, T[n]=1T[n] = 1T[n]=1.

How do the fragments come together (AKA, how do we compute the values that we have agreed to store)?

Consider the issue of computing the value of T[i]T[i]T[i] for 1⩽i⩽n1 \leqslant i \leqslant n1⩽i⩽n.

We know that any optimal sequence of interest starts at A[i]A[i]A[i]. For the sake of analysis, fix an optimal solution 1⩽i1=i<i2<⋯<ik⩽n1 \leqslant i_1 = i < i_2 < \cdots < i_k \leqslant n1⩽i1​=i<i2​<⋯<ik​⩽n.

Now, we don’t know i2i_2i2​: but what if we try to guess i2i_2i2​? That is, go over all possible choices for the starting point: all j>ij > ij>i such that A[j]>A[i]A[j] > A[i]A[j]>A[i]? For each such jjj, T[j]T[j]T[j] is the answer that we are looking for: since we don’t know which jjj is the right one a-priori, we simply consolidate all of them with a max⁡\maxmax as follows:

T[i]=1+max⁡{j∣j>i,A[j]>A[i]}T[j], for any i<n.T[i] = 1 + \max_{\{j | j > i, A[j] > A[i]\}} T[j], \text{ for any } i < n.T[i]=1+{j∣j>i,A[j]>A[i]}max​T[j], for any i<n.

Note that if the max⁡\maxmax is taken over an empty set (this would happen if A[j]<A[i]A[j] < A[i]A[j]<A[i] for all j>ij > ij>i), then the output of max⁡(⋅)\max(\cdot)max(⋅) is 000.

Can we put the pieces together without getting stuck (AKA, are the dependencies in step #4 acyclic)?

Note that computing T[i]T[i]T[i] involves pinging a bunch of entries, but all of them are of the form T[j]T[j]T[j] for some j>ij > ij>i. Thus if we compute the entries T[i]T[i]T[i] in the order:

T[n],T[n−1],⋯ ,T[1],T[n], T[n-1], \cdots, T[1],T[n],T[n−1],⋯,T[1],

then we will be just fine.

Time and Space Complexity

For the first approach:

  • We filled out O(n2)O(n^2)O(n2) entries at O(1)O(1)O(1) cost each, so the overall running time of the algorithm is O(n2)O(n^2)O(n2).
  • We store O(n2)O(n^2)O(n2) entries, although with a careful implementation the storage can be limited to O(n)O(n)O(n).

For the second approach:

  • We filled out O(n)O(n)O(n) entries at O(n)O(n)O(n) cost each, so the overall running time of the algorithm is O(n2)O(n^2)O(n2) again.
  • We store O(n)O(n)O(n) values.

Correctness

The correctness of the computations of the TTT-values specified by both approaches above is implicit in the design of the recurrence. However, we provide an explicit proof for the second approach here. The proof of correctness for most of the other recurrences that we will discuss can be established with a similar approach in spirit and is left as an exercise.

Correctness of the DP

Theorem. Let T[i]T[i]T[i] be given by:

T[i]=1+max⁡{j∣j>i,A[j]>A[i]}T[j], for any i∈[n],T[i] = 1 + \max_{\{j | j > i, A[j] > A[i]\}} T[j], \text{ for any } i \in [n],T[i]=1+{j∣j>i,A[j]>A[i]}max​T[j], for any i∈[n],

and let O[i]O[i]O[i] denote the length of a LIS in SiS_iSi​. Then T[i]=O[i]T[i] = O[i]T[i]=O[i].

Proof.

Part 1. The DP stores a value that is achievable.

First, let us show that O[i]⩾T[i]O[i] \geqslant T[i]O[i]⩾T[i], that is, there is an subsequence of length T[i]T[i]T[i] that begins at A[i]A[i]A[i]. We prove this by backward induction over iii.

The base case. T[n]=1T[n] = 1T[n]=1, and this is witnessed by the subsequence {A[n]}\{A[n]\}{A[n]}.

The induction step. If {j∣j>i,A[j]>A[i]}=∅\{j | j > i, A[j] > A[i]\} = \emptyset{j∣j>i,A[j]>A[i]}=∅, then T[i]=1T[i] = 1T[i]=1 and this is witnessed by the subsequence {A[i]}\{A[i]\}{A[i]}.

Otherwise, Let j⋆j^\starj⋆ be the index that witnesses the max⁡\maxmax value (if there are multiple indices that fit this bill, choose any). Note that j⋆>jj^\star > jj⋆>j. By the induction hypothesis, there is an subsequence of length T[j⋆]T[j^\star]T[j⋆] that begins at A[j⋆]A[j^\star]A[j⋆]. Prepend to this subsequence the value A[i]A[i]A[i], this is clearly an increasing subsequence of length 1+T[j⋆]1 + T[j^\star]1+T[j⋆], and we are done.

Part 2. The value computed by the DP cannot be beaten.

First, let us show that O[i]⩽T[i]O[i] \leqslant T[i]O[i]⩽T[i], that is, the length of any longest subsequence in SiS_iSi​ is at most T[i]T[i]T[i]. We prove this by backward induction over iii.

The base case. O[n]=1O[n] = 1O[n]=1, and T[n]=1T[n] = 1T[n]=1, so there is nothing to prove.

The induction step. Here we proceed by contradiction. Suppose there is a subsequence (say σ⋆\sigma^\starσ⋆) in SiS_iSi​ whose length is k⩾T[i]+1k \geqslant T[i] + 1k⩾T[i]+1.

Consider the tail of σ⋆\sigma^\starσ⋆, that is, the entire subsequence with the first value — i.e, A[i]A[i]A[i] — omitted. Denote this subsequence by σ\sigmaσ.

Note that σ\sigmaσ has k−1k-1k−1 elements. Suppose the first element of σ\sigmaσ is A[ℓ]A[\ell]A[ℓ]. Then σ∈Sℓ\sigma \in S_\ellσ∈Sℓ​. Note also that ℓ>i\ell > iℓ>i and A[ℓ]>A[i]A[\ell] > A[i]A[ℓ]>A[i], so T[ℓ]⩾k−1T[\ell] \geqslant k-1T[ℓ]⩾k−1 (by the induction hypothesis). Thus:

T[i]=1+max⁡{j∣j>i,A[j]>A[i]}T[j]⩾1+T[ℓ]⩾1+(k−1)=k⩾T[i]+1,T[i] = 1 + \max_{\{j | j > i, A[j] > A[i]\}} T[j] \geqslant 1 + T[\ell] \geqslant 1 + (k-1) = k \geqslant T[i] + 1,T[i]=1+{j∣j>i,A[j]>A[i]}max​T[j]⩾1+T[ℓ]⩾1+(k−1)=k⩾T[i]+1,

a contradiction.

Footnotes

  1. Indeed, since we already have a kickstart at the fragment T[n]T[n]T[n], if we can figure out a way of recovering the value of T[i]T[i]T[i] assuming knowledge of T[j]T[j]T[j] for j>ij > ij>i, then we are done: we can use this method to go backwards starting from T[n]T[n]T[n] all the way back to T[1]T[1]T[1].↩︎


© 2022 • Neeldhara Misra • Credits •

 

Corrections? Please leave a comment here or a PR in this repository, thanks!

I’d rather be a failure at something I love than a success at something I hate.

George Burns

You live and you learn — at any rate, you live.

Douglas Adams

A problem worthy of attack proves its worth by fighting back.

Paul Erdos

×