![]() |
VOOZH | about |
Given two arrays, a[] and b[], find the length of the longest common increasing subsequence(LCIS).
A subsequence is a sequence that appears in the same relative order in both arrays, but not necessarily consecutively.
The Longest Common Increasing Subsequence (LCIS) is defined as the longest sequence that appears in both arrays and whose elements are in strictly increasing order.
Examples:
Input: a[] = [3, 4, 9, 1], b[] = [5, 3, 8, 9, 10, 2, 1]
Output: 2
Explanation: The longest increasing subsequence that is common is {3, 9} and its length is 2.Input: a[] = [1, 1, 4, 3], b[] = [1, 1, 3, 4]
Output: 2
Explanation: There are two common subsequences {1, 4} and {1, 3} both of length 2.
Table of Content
We need to find a sequence that is common in both arrays and also increasing. If we look closely, we have to compare both arrays and check for elements that appear in both while maintaining the increasing order.
Now, in such problems, we often have choices when two elements are common, we must decide whether to include that element in our subsequence or skip it. Because we have multiple possibilities and need to find the longest valid sequence, recursion naturally fits well here.
The idea behind the recursive approach is we start traversing from the end of both arrays. At every step, we compare the current elements of both arrays.
- If a[i] == b[j], then this element can be a part of our LCIS β but only if it is smaller than the next element that we have already included in the subsequence (since we are moving from the end toward the beginning and building the sequence in reverse order).
- If the element cannot be included, we explore both possibilities: Move one step back in the first array or Move one step back in the second array.
- We then take the maximum result from both choices to ensure we find the longest possible sequence.
So, the recursive relation will explore all combinations to check which common increasing subsequence is the longest.
2
If we look carefully at the above recursive approach, we can notice that there are many subproblems that we solve again and again during recursion. For example, while calculating the LCIS for a particular combination of indices (i, j, prevIndex), the same state might get recomputed multiple times when reached through different recursive paths. To overcome this issue, we use Memoization (Top-Down Dynamic Programming).
The main idea is to store the results of already computed subproblems so that whenever we encounter the same state again, we can directly use the stored result instead of recomputing it. In this problem, since our recursive function depends on three changing parameters β i, j, and prevIndex β we will create a 3D DP array to store the results of these states. Before solving any subproblem, we first check in the DP array whether the result for the current combination of (i, j, prevIndex) has already been computed.
If itβs already available, we simply return it. Otherwise, we compute it recursively and store the result in the DP table for future use.
2
In the previous memoization approach, we used recursion along with a 3D DP array to store already computed results. Although memoization avoids recomputation, it still involves recursive calls, which introduce function call overhead and consume extra stack space.
To make the solution more efficient, we can convert it into an iterative tabulation approach, where we build the solution from the base cases upward this removes the recursion overhead and gives a clearer flow of computation.
We create a 3D DP table dp[i][j][prevIndex + 1], where each cell represents the length of the LCIS. Just like in memoization, we iterate over all indices i and j of arrays a and b. For every combination of (i, j, prevIndex), we compute two possibilities:
Include the element - if element of a and b same and the increasing condition holds:
dp[i][j][prevIndex + 1] = 1 + dp[i-1][j-1][i+1]Exclude the element - skip one element from either a or b, and take the best of both:
dp[i][j][prevIndex + 1] = max(dp[i-1][j][prevIndex + 1], dp[i][j-1][prevIndex + 1])We fill this table iteratively starting from smaller indices and gradually building up to larger ones until we reach dp[m-1][n-1][0], which gives the final answer.
2
In the previous 3D DP approach, we observed redundant computations and high memory usage. To optimize both time and space, we can use a 1D DP approach that finds the LCIS efficiently using only linear space.
We iterate through each element of a[], and for every a[i], we compare it with all elements of b[] from left to right. We maintain a variable currentLength that tracks the best LCIS length so far for elements smaller than the current a[i].
While comparing elements, three cases arise:
Case 1: a[i] == b[j]
The current element can be part of an increasing subsequence common to both arrays, so we update:
dp[j] = max(dp[j], currentLength + 1)
This ensures we extend the subsequence correctly when a match is found.Case 2: a[i] > b[j] Since b[j] is smaller, it can appear before a[i] in an LCIS. So, we update:
currentLength = max(currentLength, dp[j])
This step stores the best subsequence length that can later be extended when a match occurs.Case 3: a[i] < b[j], b[j] cannot come before a[i] in an increasing subsequence, so we skip it.
After processing all elements, the dp[] array holds the LCIS lengths ending at each index of b[]. The maximum value in dp[] gives the length of the Longest Common Increasing Subsequence.
2