Longest Decreasing Subsequence
You are given an array/list ARR consisting of N integers. Your task is to find the length of the longest decreasing subsequence.
A subsequence is a sequence of numbers obtained by deleting zero or more elements from the array/list, keeping the relative positions of elements same as it was in the initial sequence. A decreasing subsequence is a subsequence in which every element is strictly less than the previous number.
Note:
There can be more than one subsequences with the longest length.
For example:-
For the given array [5, 0, 3, 2, 9], the longest decreasing subsequence is of length 3, i.e. [5, 3, 2]
Note:
Try to solve the problem in O(N log N) time complexity.
Input Format :
The first line of input contains an integer 'T' representing the number of the test case. Then the test case follows.
The first line of each test case contains an integer ‘N’ representing the size of the array/list.
The second line of each test case contains N single space-separated integers representing the array/list elements.
Output Format :
For each test case, print the integer denoting the length of the longest decreasing subsequence.
Print the output of each test case in a separate line.
Note:
You do not need to print anything; it has already been taken care of. Just implement the given function.
Constraints:
1 <= T <= 50
1 <= N <= 5000
1 <= ARR[i] <= 10^9
Time Limit: 1 sec
We can use recursion to solve this problem.
- For each element, there are two possibilities.
- We include the current item in LDS if it is smaller than the previous element and recurse for remaining items.
- We exclude the current item from LDS and recurse for remaining items.
- Finally, we return the length of LDS we get by including or excluding the current item.
- The base case of the recursion would be to return 0 when no items are left.
In the recursive approach, many recursive calls had to be made again and again with the same set of parameters. This redundancy can be eliminated by storing the results obtained for a particular call in memory (as an array, MEMO) so that whenever we need to call the function again if we have stored its value earlier.
This can be done as:
- For each element, there are two possibilities.
- We include the current item in LDS if it is smaller than the previous element and recurse for remaining items. For this, we first check if we have value for LDS stored in MEMO.
- If MEMO[currentElement] > -1, then return MEMO
- Else calculate the value for currentElement and store it in MEMO[currentElement] and return it.
- We exclude the current item from LDS and recurse for remaining items.
- We include the current item in LDS if it is smaller than the previous element and recurse for remaining items. For this, we first check if we have value for LDS stored in MEMO.
- Finally, we return the length of the LDS we get by including or excluding the current item.
- The base case of the recursion would be to return 0 when no items are left.
The problem has optimal substructure. That means the problem can be broken down into smaller sub-problems, which can further be divided until the solution becomes trivial.
The solution also exhibits overlapping sub-problems. If we draw the recursion tree of the solution, we can see that the same sub-problems are getting computed again and again.
We know that problems having optimal substructure and overlapping subproblems can be solved by using Dynamic Programming.
We’ll solve this problem in a bottom-up manner. In this bottom-up approach, we will solve smaller subproblems first, then larger sub-problems from them.
- We will compute L[i], for each 0 <= i < N, which stores the length of the longest decreasing subsequence of the subarray arr[0..i] that ends with arr[i].
- We will initialise L[i] = 1, for each 0 <= i < N because the longest decreasing subsequence ending with ARR[i] has length 1.
- To calculate L[i], for each 1 <= i < N, we iterate over L[j], for each 0 <= j < i.
- If the value at index j is greater than the value at index i, then we can append the current element (at i-th index) to the LDS ending at index j. Hence, we update L[i] = max(L[i], 1 + L[j]).
- Return the maximum value of all L[i], for 0 <= i <N.
The idea here is to maintain lists of decreasing sequences. In general, we have a set of active lists of varying length. We are adding an element A[i] to these lists. We can always add A[i] at a point just larger than it and we can show that this doesn't hamper the LDS. We scan the lists (for end elements) in decreasing order of their length. We will verify the end elements of all the lists to find a list whose end element is smaller than A[i] (floor value).
The basic conditions to create these lists will be:
- If A[i] is largest among all end candidates of active lists, we will start a new active list of length 1.
- If A[i] is smallest among all end candidates of active lists, we will clone the largest active list, and extend it by A[i].
- If A[i] is in between, we will find a list with the smallest end element that is larger than A[i]. Clone and extend this list by A[i].
We will discard all other lists of the same length as that of this modified list.
Note: At any instance during our construction of active lists, the following condition is maintained.
“The end element of a smaller list is smaller than the end elements of larger lists”.