AfterAcademy Tech
•
07 Apr 2020

Level: Medium
Asked In: Amazon, Facebook, Microsoft
Problem Description: A subsequence is derived from an array by deleting a few of its elements and not changing the order of remaining elements. You are given an array A with N elements, write a program to find the longest increasing subsequence in the array.
An increasing subsequence is a subsequence with its elements in increasing order. You need to find the length of the longest increasing subsequence that can be derived from the given array.
For example:
Input: A = {3, 10, 2, 1, 20}
Output: 3
Explanation: The longest increasing subsequence is {3,10,20}.
Input: A = {10, 2, 5, 3, 7, 101, 18}
Output: 4
Explanation: The longest incresing subsequence is {2,3,7,101} or {2,3,7,18} or {2,5,7,101} or {2,5,7,18}.
Possible questions to ask the interviewer →
We will be discussing 4 possible solutions to solve this problem:-
Let’s try to learn by taking an example.
arr[] = {10, 2, 5, 3, 7, 101, 18}
Thinking of extracting a subsequence by code may be hard because it can start anywhere, end anywhere and skip any number of elements. Let us fix one of these factors then. For each element, we will find the length of the Longest Increasing Subsequence(LIS) that ends at that element.
This way, we have fixed our ending point. Now that we have established the last element of the subsequence, what next? We will proceed recursively. What are the possible second-last elements of the subsequence? All elements with value lesser than the current element that appears on the left of current element, right?
That’s it right there! That’s the basis of our recurrence relation.
lis_ending_here(arr, i) = 1 + lis_ending_here(arr, j)
--> j < i and arr[j] < arr[i]
If we do this for each element, we will have our answer.
Solution Steps
We will need to use a helper function to ease our implementation.
Pseudo-Code
int lis_ending_here(int arr[], int curr)
{
// Only one subsequence ends at first index, the number itself
if(curr == 0)
return 1
int ans = 1
for(i = curr-1 to 0, decrement of -1)
if(arr[i] < arr[curr])
ans = max(ans, 1 + lis_ending_here(arr, i))
return ans
}
int longest_increasing_subsequence(int arr[], int N)
{
// Because a single number can be a subsequence too
int max_ans = 1
for(i = 0 to N-1)
max_ans = max(max_ans, lis_ending_here(arr, i))
return max_ans
}
Complexity Analysis
Recurrence relation: T(N) = 1 + Sum j = 1 to N-1 (T(j))
Time Complexity: O(2^N) (Think!)
Space Complexity: O(N), for stack space in recursion
Critical ideas to think!
Create a recursion tree for the above recursion.

As you can clearly see in the recursion tree, there are overlapping subproblems and also holds an optimal substructure property. This means we could improve the time complexity of our algorithm using Dynamic Programming.
Elements of Dynamic Programming
Well, the recursion approach above is top-down. This means the implementation of our dynamic programming should be bottom-up. What are the other elements of dynamic programming we need to figure out?
1. Define problem variables and decide the states: There is only one parameter on which the state of the problem depends i.e. which is N here, the size of the array.
2. Define Table Structure and Size: To store the solution of smaller sub-problems in bottom-up approach, we need to define the table structure and table size.
3. Table Initialization: We can initialize the table by using the base cases from the recursion. (Think)
4. Iterative Structure to fill the table: We can define the iterative structure to fill the table by using the recurrence relation of the recursive solution.
lis[i] = lis[j] + 1
--> j < i and arr[j] < arr[i]
5. Termination and returning final solution: After filling the table in a bottom-up manner, we have the longest increasing subsequence ending at each index. Iterate the auxiliary array to find the maximum number. It will be the longest increasing subsequence for the entire array
Solution Steps
Pseudo-Code
int longest_increasing_subsequence(int arr[], int N)
{
int lis[N]
for(i = 0 to N-1)
lis[i] = 0
lis[0] = 1
for(i = 1 to N-1)
{
for(j = 0 to i-1)
{
if(arr[j] < arr[i])
lis[i] = max(lis[i], lis[j] + 1)
}
}
int ans = 1
for(i = 0 to N-1)
ans = max(ans, lis[i])
return ans
}
Complexity Analysis
For each element, we traverse all elements on the left of it.
Time Complexity: O(N²)(Think!)
Space Complexity: O(N), for storing the auxiliary array
Critical ideas to think!
There also exists a greedy approach to this problem. But how can a problem have both dynamic and greedy approaches? Of course, it's possible. Dynamic Programming was chosen just because there were overlapping subproblems and optimal substructure. This doesn’t mean a greedy approach is not possible.
We will use a variant of patience sorting to achieve our goal. But what is patience sorting? Well, let us try to understand this approach by visualizing an example using a deck of cards.
→ Assume you have a certain permutation of a deck of cards with all cards face up in front of you. Your task is to divide the cards into piles:-
Now let’s start placing cards in piles…

Patience Sorting involves merging these k-sorted piles optimally to obtain the sorted list. But our objective is attained in the first phase of this algorithm. Didn’t you notice?
The pile with the most number of cards is our longest increasing subsequence. Easy, right? You can do the same when you’re given a list of numbers.
How will you implement it? (Think!)
Solution Steps
Our algorithm is divided into two phases, select the first pile suited to place the number in and then place the element in that pile.
Pseudo-Code
int longest_increasing_subsequence(int arr[], int N)
{
int pile_top[N], pile_length[N]
for( i = 0 to N )
pile_top[i] = INT_MAX
for( i = 0 to N )
pile_length[i] = 0
for( i = 0 to N )
{
for( j = 0 to N )
{
if(pile_top[j] > arr[i])
{
pile_top[j] = arr[i]
pile_length[j] += 1
break
}
}
}
int ans = 0
for( i = 0 to N )
ans = max(ans, pile_length[i])
return ans
}
Complexity Analysis
For each element in the array, we select the first pile that has the top element higher than the current element. The number of piles can be maximum up to length N. So there are N elements in the array and for each of them, we need to search another list of maximum length N.
Time Complexity: O(N) * O(N) = O(N²) (Why?)
Space Complexity: O(N) + O(N) = O(N), for storing two arrays
Critical ideas to think!
As the title must’ve hinted you by now, we will use Binary Search to select the pile. But isn’t it true that binary search can only be applied to sorted arrays? Yeah, so? Notice that the pile_top[] array is sorted in nature. (Print the array if you feel so, to check!).
Basically, our purpose in the searching phase is → We are given a sorted array and we need to find the first number in the array that is greater than the current element.(Try to understand how our problem got reduced to this problem).
Conclusion: We now need to find the upper bound of each element in the pile_top[] array.
Its sometimes easier and faster to solve a problem by trying to divide it into smaller subproblems and solving them individually and optimally.
So now we need to find the upper bound of the given number in the array. Upper bound can be found in O(logn) using a variation of binary search.
Solution Steps
The solution steps for this algorithm are quite similar to the one stated in the previous approach, except for the searching phase. We will find the upper bound of the array elements in the pile_top[] array. Let us discuss the steps to find the upper bound of a given element in an array.
Given array = arr[], given element = item
4. Return low
Pseudo-Code
int upper_bound(int arr[], int item)
{
int low = 0, high = arr.length()-1
while(low <= high)
{
int mid = (low + high)/2
if(arr[mid] <= item)
low = mid+1
else
high = mid
}
return low
}
int longest_increasing_subsequence(int arr[], int N)
{
int pile_top[N], pile_length[N]
for( i = 0 to N )
pile_top[i] = INT_MAX
for( i = 0 to N )
pile_length[i] = 0
for( i = 0 to N )
{
int j = upper_bound(pile_length, arr[i])
pile_top[j] = arr[i]
pile_length[j] += 1
}
int ans = 0
for( i = 0 to N )
ans = max(ans, pile_length[i])
return ans
}
Complexity Analysis
Time Complexity: Find upper bound for each element in the array = O(N) * O(logn) = O(Nlogn)
Space Complexity: O(N) + O(N) = O(N), for storing the two auxiliary arrays
Critical ideas to think!

Happy Coding!
Team AfterAcademy 🙂
AfterAcademy Tech
Given an unsorted array A[] consisting of n integers, you need to find the length of the longest consecutive sequence of integers in the array.

AfterAcademy Tech
This is a mathematical problem asked in interviews of Google like companies. For solving such problems you need to have some math skills and also some idea of algorithms like Euclid's GCD algorithm.

AfterAcademy Tech
This is an interview problem asked in companies like Google, Microsoft, Amazon. The problem is to remove duplicates from a sorted array or list.

AfterAcademy Tech
This is an interview problem asked in companies like Amazon, Microsoft. The problem is to merge two binary trees. The overlapping nodes are replaced by the sum of corresponding nodes. We are discussing two ways of solving this.
