The longest increasing subsequence (LIS) problem is one of the most common in competitive programming. Given a sequence A, we want to find some sequence A_1, A_2 … A_k such that A_1 < A_2 < A_3 < A_k, hence a strictly increasing subsequence. Additionally, we want to find the subsequence with the maximum possible length. 

Naively, we can do this task with O(N^2) dynamic programming. 

Let dp[i] = maximum length subsequence ending at index i

dp[0] = 0 

Then dp[i] = max(dp[j]) + 1 such that j < i and a[j] < a[i]. 

int lis(vector<int> a){
    vector<int> dp(a.size());
    for(int i = 0; i < a.size(); i++){
        dp[i] = 1; //length is always at least 1 
        for(int j = 0; j < i; j++){
            if(a[i] > a[j]){
                dp[i] = max(dp[i], dp[j] + 1);
            }
        }
    }
    return *max_element(dp.begin(), dp.end());
}

Fast solution: O(NlogN)

Let d_j[i] = the smallest element of A such that there exists an increasing subsequence of length i ending in said element. (If we only consider the prefix of elements up to index j). 

For example if we have the sequence A = {1, 5, 3, 4}

Then d_3 = {-INF, 1, 3, INF}. This is because a sequence of length 0 will always end with smallest element -INF, a sequence of length 2 is {1, 3} which ends with 3 which is minimal, etc. 

Notice that d_j is always increasing. 

Next, notice that when we want to calculate the LIS for the prefix ending at index i+1, then the answer will be the least j such that d_i[j] >= A[i+1]. This is because after we cannot extend the sequence further since all elements are larger than A[i+1]. 

Also note that we only update one index every single time we process a new value of d_i, so we can dynamically maintain d. 

int lis(vector<int> a){
    vector<int> dp;
    for(int u : a){
        int p = lower_bound(dp.begin(),dp.end(), u) – dp.begin(); //find first greater than equal to
        if(p == dp.size())dp.push_back(p); //extend the sequence
        else dp[p] = u; //make the ending smaller  
    }
    return dp.size();
}