


Input: ‘n’ = 5, ‘arr’ = [50, 3, 90, 60, 80].
Output: 2
Explanation:
In this array, the longest increasing subsequences are [50, 60, 80] and [3, 60, 80].
There are other increasing subsequences as well, but we need the number of the longest ones. Hence the answer is 2.
The first line of each test case contains a single integer ‘n’ denoting the length of the array ‘arr’.
The following line contains ‘n’ space-separated integers representing elements in ‘arr’.
For each test case, return an integer representing the number of longest increasing subsequences.
You are not required to print anything. It has already been taken care of. Just implement the function.
The simplest approach is to generate all possible subsequences present in the given array ‘arr’ and count the increasing subsequences of maximum length. Print the count after checking all subsequences.
For every element in the array, there are two choices, either to include it in the subsequence or not include it. Apply this for every element in the array starting from index 0 until we reach the last index. After we reach the last index, we check if this is an increasing subsequence and if so, then we increment the count of the size of this subsequence by 1.
At last, we simply traverse the count array and find the frequency of the max count.
In the above approach, we were generating all possible subsequences. It will definitely result in TLE. We can think of a solution where we can use something we already calculated. We can maintain a ‘length’ array where ‘length’[i] denotes the length of LIS ending at ‘arr’[i]. We also maintain an array ‘count’ wherein each entry ‘count’[i] we store the number of LISs whose size equals 'length’[i].
'length’[i] can be recursively calculated as:
‘length’[i] = 1 + max( ‘length’[j] ) where 0 < j < i and ‘arr’[j] < ‘arr’[i]; or
'length’[i] = 1, if no such j exists.
But if we calculate this directly with the help of recursion, it will also lead to TLE because the time complexity is exponential ~O(N ^ N).
Can we do better?
We can see that there are many subproblems in the above recursive solution which are solved again and again. So this problem has Overlapping Substructure property and recomputation of same subproblems can be avoided by either using Memoization or Tabulation.
We can avoid recomputation of subproblems by using tabulation as shown below
Let's take an example ‘arr’ = [1,4,2,5,3].
Initially, all elements in 'length’ and ‘count’ are 1.
We start from ‘i’ equal to 1 i.e. ‘num’ is 4.
We see that 4 is greater than 1 and 'length’[0] + 1 is greater than 'length’[1], so we update 'length’[1] to 'length’[0] + 1 and ‘count’[1] to ‘count’[0].
Then we go to ‘i’ equal to 2 i.e. ‘num’ is 2.
We see that 2 is greater than 1 and 'length’[0] + 1 is greater than 'length’[2], so we update 'length’[2] to 'length’[0] + 1 and ‘count’[2] to ‘count’[0].
Then we go to ‘i’ equal to 3 i.e. ‘num’ is 5.
We see that 5 is greater than 1 and 'length’[0] + 1 is greater than 'length’[3], so we update 'length’[3] to 'length’[0] + 1 and ‘count’[3] to ‘count’[0].
Again we see that 5 is greater than 4 and 'length’[1] + 1 is greater than 'length’[3], so we update 'length’[3] to 'length’[1] + 1 and ‘count’ to ‘count’[1].
Again we see that 5 is greater than 2 and 'length’[2] + 1 is equal to 'length’[3], so we update ‘count’[3] to ‘count’[3] + ‘count’[2].
Now we go to ‘i’ equal to 4 i.e. ‘num’ is 3.
We see that 3 is greater than 1 and 'length’[0] + 1 is greater than 'length’[4], so we update 'length’[4] to 'length’[0] + 1 and ‘count’[4] to ‘count’[0].
Again we see that 3 is greater than 2 and 'length’[2] + 1 is greater than 'length’[4], so we update 'length’[4] to 'length’[2] + 1 and ‘count’[4] to ‘count’[2].
Now the ‘length’ array becomes [1, 2, 2, 3, 3]. So ‘maxLength’ is 3.
The ‘count’ array is [1, 1, 1, 2, 1].
Now we traverse the 'length’ array and observe that 'length’[3] and 'length’[4] are the same as ‘maxLength’ so we incremented ‘cnt’ by ‘count’[3] and ‘count’[4] respectively.
Hence the ‘cnt’ becomes 3 which is our answer, and the three LIS are [1, 4, 5], [1, 2, 5], [1, 2, 3].
Now we have already discussed the O( n ^ 2 ) solution. There is one more optimal solution to this problem. We can solve this problem in O( n * log(n) ) time using binary search.
The idea is simple; we keep track of the longest increasing sequence (LIS) as well as the number of LIS sequences ending with the current element.
Check the code for a better understanding.
For example, let 'arr' = [1, 3, 5, 4, 7]
I assume it is easy to see LIS for the first 3 elements is [1, 3, 5].
When we see 4, we can update LIS to have [1,3,4], but we should not discard the sequence [1, 3, 5] since it may contribute to later LIS sequences. This LIS has a length of 3 (maximum index is 2), so we keep a vector:
'table'[2] = {[5,1], [4,1]}, indicating at LIS index 2, we could have 1 sequence ending with 5 and another sequence ending with 4.
Then we see 7. We see 7 has to be added to the end of the previous LIS, but it could be added to the LIS ending with 5 or 4. So we will have 'table'[3] = {[7, 2]}.
In general, for any current element, we will need to find out the index just before its insert point in LIS. In the previous example, for number 7 to be inserted at index 3, we need to check 'table'[2] to update the number of possible paths for the current number. In my program, this is what ‘k’ is.
Also, for each LIS insertion at index ‘i’, we are inserting it in descending order, so it would be easy to find out the exact place in 'table'[i] using binary search. In my program, I used negative numbers for easy binary search.
Another trick is I am only keeping the prefix sum of all paths for 'table'[i]. In this way, the number of paths ‘k’ can be obtained in O(1).
Largest Plus Sign
Minimized Maximum of Products Distributed to Any Store
Optimal Itinerary for Maximum Profit
Count of Subsequences with Given Sum
Optimal Line Arrangement