Longest Common Subsequence with no Repeating Character in C++

The Longest Common Subsequence (LCS) problem is a classic dynamic programming problem that aims to find the length of the longest subsequence common to two given sequences.

Algorithm:

  • Initialize a 2D array (matrix):

Create a 2D array dp with dimensions (m + 1) x (n + 1), where m and n are the lengths of the two input strings.

  • Fill in the matrix:

Iterate through the characters of both strings using two nested loops. Let the loop indices be i and j, ranging from 0 to m and 0 to n.

  • Check character equality and non-repeating condition:

At each position (i, j) in the matrix, check if the characters s1[i-1] and s2[j-1] are equal and non-repeating in their respective substrings.

If they are equal and non-repeating, set dp[i][j] to dp[i-1][j-1] + 1.

If they are not equal, set dp[i][j] to the maximum of dp[i-1][j] and dp[i][j-1].

  • Final Result:

The bottom-right cell dp[m][n] will contain the length of the LCS with no repeating characters.

Program:

Output:

Length of the longest common subsequence with no repeating characters: 2

Complexity analysis:

Time Complexity:

The time complexity of the algorithm is O(m * n), where 'm' and 'n' are the lengths of the input strings s1 and s2. This is because there is a nested loop that iterates through each character of both strings, and the work done inside the loop is constant time.

Space Complexity:

The space complexity is O(m * n) as well. This is due to the 2D vector dp of dimensions (m + 1) x (n + 1) that is used to store the lengths of the LCS for each pair of substrings. The space complexity is directly proportional to the size of this matrix.

In the worst case, the entire matrix needs to be filled, leading to O(m * n) space complexity.

Approach 1: Using a Recursive Approach with Memorization

The recursive approach with memorization involves breaking down the problem into smaller subproblems and caching the results of these subproblems to avoid redundant calculations.

Program:

Output:

Length of the longest common subsequence with no repeating characters: 0

Explanation:

  • Recursive Function:

The main function is lcsRecursiveWithMemo, which calculates the length of the LCS with no repeating characters using a recursive approach.

Base Case:

  • The function has a base case to stop the recursion:

When either of the indices (i or j) reaches 0, indicating the end of one of the input strings. In such cases, the LCS length is considered 0.

  • Memorization:

Memorization is used to optimize the recursive solution. It involves storing and reusing previously computed results to avoid redundant calculations.

A memorization table (unordered_map) is used to store results based on the combination of current indices i and j.

  • Non-Repeating Character Check:

The original code used the count function to check for non-repeating characters. However, an alternative approach using explicit loops is implemented due to a compilation error.

The function checks if the current character being considered in the LCS is non-repeating in its respective substring. If it is repeating, the LCS cannot have repeating characters.

  • Recursive Logic:

If the characters at the current positions are equal and non-repeating, the function recursively calculates the LCS length for the previous positions (i-1, j-1) and adds 1 to it.

If the characters are not equal, it takes the maximum of the LCS lengths obtained by excluding either the last character of s1 or the last character of s2.

  • Result:

The final result is obtained by calling the recursive function with the lengths of the input strings as initial indices. The result represents the length of the longest common subsequence with no repeating characters.

  • Main Function:

The longestCommonSubsequenceNoRepeatingCharRecursive function serves as an entry point, initializing the memorization table and calling the recursive function.

  • Example Usage:

The example in the main function demonstrates the usage with strings "ABCBDAB" and "BDCAB". After that, the result is printed to the console.

Complexity analysis:

Time Complexity:

The time complexity of the algorithm is O(m * n), where 'm' and 'n' are the lengths of the input strings s1 and s2.

With memorization, the function avoids redundant computations by storing and reusing results for already solved subproblems. This significantly reduces the number of recursive calls, making the time complexity more efficient than the naive recursive approach.

Space Complexity:

Due to the memorisation table, the space complexity is O(m * n). The table stores results for all unique combinations of indices (i, j) during the recursive calls. In the worst case, the entire table needs to be filled, leading to O(m * n) space complexity.

Apart from the memorization table, the space complexity also includes the recursive call stack, which can go up to the maximum depth of the recursion tree. In the worst case, it would be O(m + n).