A subsequence is obtained by deleting any number of elements while keeping the order of the remaining elements. The chosen elements do not need to be next to each other.
For example, [1,5,6] is a subsequence of [3,1,5,2,6], while [5,1,6] is not: in the original array, appears after .
5
1
In the longest increasing subsequence problem, or LIS, we want to choose as many elements as possible so that every next chosen value is strictly larger than the previous one.
Problem and first attempts
You are given an array a[1…n]. Find the maximum length k for which there are indices
i1<i2<⋯<ik
satisfying
a[i1]<a[i2]<⋯<a[ik]
We will also restore one such subsequence.
For a=[3,1,5,2,6,4,9], two longest increasing subsequences are [3,5,6,9] and [1,2,4,9]. Both have length 4.
The most direct solution generates every subsequence. Each element may be taken or skipped, so there are 2n possibilities. This works only for very small n.
A greedy choice is tempting, but unreliable. Consider [1,100,2,3,4]. If we take 100 immediately after 1, we get stuck with a subsequence of length 2. The better answer is [1,2,3,4].
The problem is that an element can be useful or harmful depending on what comes later. Dynamic programming lets every position look back and choose its own best predecessor.
The state
Let dp[i] be the maximum length of an increasing subsequence whose last chosen element is exactly a[i].
The phrase “ends exactly at i” is the key. Once we require a[i] to be last, the previous chosen element may be any a[j] such that j<i and a[j]<a[i].
Every single element is already an increasing subsequence, so the base value is
dp[i]=1
For every valid predecessor j, we may append a[i] to a subsequence ending at j. Therefore, we try the transition
dp[i]=max(dp[i],dp[j]+1)
The LIS may end at any position, so the final answer is
1≤i≤nmaxdp[i]
It is not necessarily dp[n].
To restore a subsequence, we also store parent[i]: the predecessor j used when the best value of dp[i] was obtained. If no predecessor is used,
parent[i]=0
Main algorithm
We process the array from left to right. When calculating the state for position i, every earlier state is already known.
For each i, start with a subsequence containing only a[i]. Then inspect every earlier position j. If a[j]<a[i] and extending the subsequence from j improves the answer, update both dp[i] and parent[i].
Algorithm:
There are n states, and each state checks at most n earlier positions. This reduces the exponential search to O(n2) time.
Implementation
Complexity
Time:O(n2)
Memory:O(n)
1. Set answer = 0 and last = 0.
2. For every position i from 1 to n:
2.1. Set dp[i] = 1 and parent[i] = 0.
2.2. For every position j from 1 to i - 1:
2.2.1. If a[j] < a[i] and dp[j] + 1 > dp[i]:
- Set dp[i] = dp[j] + 1.
- Set parent[i] = j.
2.3. If dp[i] > answer:
- Set answer = dp[i].
- Set last = i.
3. Create an empty sequence lis.
4. While last != 0:
4.1. Add a[last] to lis.
4.2. Set last = parent[last].
5. Reverse lis.
6. Output answer and lis.
#include <bits/stdc++.h>using namespace std;const int N = 5005;int n;int a[N];int dp[N];int parent[N];int main(){ ios_base::sync_with_stdio(false); cin.tie(nullptr); cin >> n; for(int i = 1; i <= n; i++){ cin >> a[i];} int answer = 0; int last = 0; for(int i = 1; i <= n; i++){ dp[i] = 1; parent[i] = 0; for(int j = 1; j < i; j++){ if(a[j]< a[i] && dp[j] + 1> dp[i]){ dp[i] = dp[j] + 1; parent[i] = j;}} if(dp[i]> answer){ answer = dp[i]; last = i;}} vector<int> lis; while(last != 0){ lis.push_back(a[last]); last = parent[last];} reverse(lis.begin(), lis.end()); cout << answer << endl; for(int x : lis){ cout << x << ' ';} cout << endl; return 0;}