Given string str, find the length of the longest repeating subsequence such that it can be found twice in the given string.
The two identified subsequences A and B can use the same ith character from string s if and only if that ith character has different indices in A and B. For example, A = "xax" and B = "xax" then the index of the first "x" must be different in the original string for A and B.
Examples:
Input: s = "axxzxy"
Output: 2
Explanation: The given array with indexes looks like
a x x z x y
0 1 2 3 4 5
The longest subsequence is "xx". It appears twice as explained below.
subsequence A
x x
0 1 <-- index of subsequence A
------
1 2 <-- index of s
subsequence B
x x
0 1 <-- index of subsequence B
------
2 4 <-- index of s
We are able to use character 'x' (at index 2 in s) in both subsequences as it appears on index 1 in subsequence A and index 0 in subsequence B.
Input: s = "axxxy"
Output: 2
Explanation: The given array with indexes looks like
a x x x y
0 1 2 3 4
The longest subsequence is "xx". It appears twice as explained below.
subsequence A
x x
0 1 <-- index of subsequence A
------
1 2 <-- index of s
subsequence B
x x
0 1 <-- index of subsequence B
------
2 3 <-- index of s
We are able to use character 'x' (at index 2 in s) in both subsequencesas it appears on index 1 in subsequence A and index 0 in subsequence B.
Table of Content
- [Naive Approach] Using Recursion - O(2 ^ n) time and O(n) space
- [Better Approach] Using Top-Down DP (Memoization) â O(n ^ 2) Time and O(n ^ 2) Space
- [Expected Approach - 1] Using Bottom-Up DP (Tabulation) - O(n ^ 2) Time and O(n ^ 2) Space
- [Expected Approach - 2] Using Space Optimized DP - O(n ^ 2) Time and O(n) Space
[Naive Approach] Using Recursion - O(2 ^ n) time and O(n) space
This problem is a variation of the Longest Common Subsequence (LCS) problem. We compare the string with itself and find the LCS, with an additional condition that matching characters must come from different indices. This ensures that the same character position is not used in both subsequences.
The idea is to compare the characters at index i and j of s and the indices i and j. Two cases arise:
1. If the characters of the string match (s[i-1] == s[j-1]) and their indices are different (i != j), then make a recursive call for the remaining string (i-1 and j-1) and add 1 to the result.
longestRepeatingSubsequence(i, j, s) = 1 + longestRepeatingSubsequence(i-1, j-1, s)
2. If the characters do not match or the indices are the same, make two recursive calls:
longestRepeatingSubsequence(i, j, str) = max(longestRepeatingSubsequence(i-1, j, str), longestRepeatingSubsequence(i, j-1, str))
Base Case:
If either of the strings becomes empty (i == 0 or j == 0), return 0.
#include <bits/stdc++.h>
using namespace std;
int findLongestRepeatingSubsequence(int i, int j, string &s)
{
// base case
if (i == 0 || j == 0)
return 0;
// If characters match and their
// indices are different
if (s[i - 1] == s[j - 1] && i != j)
{
return 1 + findLongestRepeatingSubsequence(i - 1, j - 1, s);
}
// Else make two recursive calls.
return max(findLongestRepeatingSubsequence(i - 1, j, s), findLongestRepeatingSubsequence(i, j - 1, s));
}
int longestRepSubseq(string s)
{
int n = s.length();
return findLongestRepeatingSubsequence(n, n, s);
}
int main()
{
string s = "axxxy";
int res = longestRepSubseq(s);
cout << res;
return 0;
}
class GFG {
static int findLongestRepeatingSubsequence(int i, int j,
String s)
{
// base case
if (i == 0 || j == 0)
return 0;
// If characters match and their
// indices are different
if (s.charAt(i - 1) == s.charAt(j - 1) && i != j) {
return 1
+ findLongestRepeatingSubsequence(i - 1,
j - 1, s);
}
// Else make two recursive calls.
return Math.max(
findLongestRepeatingSubsequence(i - 1, j, s),
findLongestRepeatingSubsequence(i, j - 1, s));
}
static int longestRepSubseq(String s)
{
int n = s.length();
return findLongestRepeatingSubsequence(n, n, s);
}
public static void main(String[] args)
{
String s = "axxxy";
int res = longestRepSubseq(s);
System.out.println(res);
}
}
def findLongestRepeatingSubsequence(i, j, s):
# base case
if i == 0 or j == 0:
return 0
# If characters match and their
# indices are different
if s[i - 1] == s[j - 1] and i != j:
return 1 + findLongestRepeatingSubsequence(i - 1, j - 1, s)
# Else make two recursive calls.
return max(findLongestRepeatingSubsequence(i - 1, j, s),
findLongestRepeatingSubsequence(i, j - 1, s))
def longestRepSubseq(s):
n = len(s)
return findLongestRepeatingSubsequence(n, n, s)
if __name__ == "__main__":
s = "axxxy"
res = longestRepSubseq(s)
print(res)
using System;
class GFG {
static int findLongestRepeatingSubsequence(int i, int j,
string s) {
// base case
if (i == 0 || j == 0)
return 0;
// If characters match and their
// indices are different
if (s[i - 1] == s[j - 1] && i != j) {
return 1
+ findLongestRepeatingSubsequence(i - 1,
j - 1, s);
}
// Else make two recursive calls.
return Math.Max(
findLongestRepeatingSubsequence(i - 1, j, s),
findLongestRepeatingSubsequence(i, j - 1, s));
}
static int longestRepSubseq(string s) {
int n = s.Length;
return findLongestRepeatingSubsequence(n, n, s);
}
static void Main(string[] args) {
string s = "axxxy";
int res = longestRepSubseq(s);
Console.WriteLine(res);
}
}
function findLongestRepeatingSubsequence(i, j, s) {
// base case
if (i === 0 || j === 0)
return 0;
// If characters match and their
// indices are different
if (s[i - 1] === s[j - 1] && i !== j) {
return 1
+ findLongestRepeatingSubsequence(i - 1,
j - 1, s);
}
// Else make two recursive calls.
return Math.max(
findLongestRepeatingSubsequence(i - 1, j, s),
findLongestRepeatingSubsequence(i, j - 1, s));
}
function longestRepSubseq(s) {
const n = s.length;
return findLongestRepeatingSubsequence(n, n, s);
}
//Driver Code
const s = "axxxy";
const res = longestRepSubseq(s);
console.log(res);
Output
2
[Better Approach] Using Top-Down DP (Memoization) â O(n ^ 2) Time and O(n ^ 2) Space
If we notice carefully, we can observe that the above recursive solution holds the following two properties of Dynamic Programming:
1. Optimal Substructure: The maximum length of repeating subsequence originating from i, j, i.e., longestRepeatingSubsequence(i, j), depends on the optimal solution of longestRepeatingSubsequence(i-1, j-1) if characters match and indices don't match. Otherwise, it depends on the maximum of longestRepeatingSubsequence(i-1, j) and longestRepeatingSubsequence(i, j-1).
2. Overlapping Subproblems: While applying a recursive approach in this problem, we notice that certain subproblems are computed multiple times.
- There are two parameters, i and j that changes in the recursive solution. So we create a 2D array of size n*n for memoization.
- We initialize this array as -1 to indicate nothing is computed initially.
- Now we modify our recursive solution to first check if the value is -1, then only make recursive calls. This way, we avoid re-computations of the same subproblems.
#include <bits/stdc++.h>
using namespace std;
int findLongestRepeatingSubsequence(int i, int j, string &s, vector<vector<int>> &memo) {
// base case
if (i == 0 || j == 0)
return 0;
// If value is computed, return it.
if (memo[i - 1][j - 1] != -1)
return memo[i - 1][j - 1];
// If characters match and their
// indices are different
if (s[i - 1] == s[j - 1] && i != j)
{
return memo[i - 1][j - 1] = 1 + findLongestRepeatingSubsequence(i - 1, j - 1, s, memo);
}
// Else make two recursive calls.
return memo[i - 1][j - 1] = max(findLongestRepeatingSubsequence(i - 1, j, s, memo),
findLongestRepeatingSubsequence(i, j - 1, s, memo));
}
int longestRepSubseq(string s) {
int n = s.length();
vector<vector<int>> memo(n, vector<int>(n, -1));
return findLongestRepeatingSubsequence(n, n, s, memo);
}
int main() {
string s = "axxxy";
int res = longestRepSubseq(s);
cout << res;
return 0;
}
class GFG {
static int findLongestRepeatingSubsequence(int i, int j,
String s,
int[][] memo) {
// base case
if (i == 0 || j == 0)
return 0;
// If value is computed, return it.
if (memo[i - 1][j - 1] != -1)
return memo[i - 1][j - 1];
// If characters match and their
// indices are different
if (s.charAt(i - 1) == s.charAt(j - 1) && i != j) {
return memo[i - 1][j - 1]
= 1
+ findLongestRepeatingSubsequence(
i - 1, j - 1, s, memo);
}
// Else make two recursive calls.
return memo[i - 1][j - 1]
= Math.max(findLongestRepeatingSubsequence(
i - 1, j, s, memo),
findLongestRepeatingSubsequence(
i, j - 1, s, memo));
}
static int longestRepSubseq(String s) {
int n = s.length();
int[][] memo = new int[n][n];
for (int[] row : memo) {
java.util.Arrays.fill(row, -1);
}
return findLongestRepeatingSubsequence(n, n, s,
memo);
}
public static void main(String[] args) {
String s = "axxxy";
int res = longestRepSubseq(s);
System.out.println(res);
}
}
def findLongestRepeatingSubsequence(i, j, s, memo):
# base case
if i == 0 or j == 0:
return 0
# If value is computed, return it.
if memo[i - 1][j - 1] != -1:
return memo[i - 1][j - 1]
# If characters match and their
# indices are different
if s[i - 1] == s[j - 1] and i != j:
memo[i - 1][j - 1] = 1 + findLongestRepeatingSubsequence(
i - 1, j - 1, s, memo
)
return memo[i - 1][j - 1]
# Else make two recursive calls.
memo[i - 1][j - 1] = max(
findLongestRepeatingSubsequence(i - 1, j, s, memo),
findLongestRepeatingSubsequence(i, j - 1, s, memo)
)
return memo[i - 1][j - 1]
def longestRepSubseq(s):
n = len(s)
memo = [[-1 for _ in range(n)] for _ in range(n)]
return findLongestRepeatingSubsequence(n, n, s, memo)
if __name__ == "__main__":
s = "axxxy"
res = longestRepSubseq(s)
print(res)
using System;
class GFG {
static int findLongestRepeatingSubsequence(int i, int j,
string s,
int[, ] memo) {
// base case
if (i == 0 || j == 0)
return 0;
// If value is computed, return it.
if (memo[i - 1, j - 1] != -1)
return memo[i - 1, j - 1];
// If characters match and their
// indices are different
if (s[i - 1] == s[j - 1] && i != j) {
memo[i - 1, j - 1]
= 1
+ findLongestRepeatingSubsequence(
i - 1, j - 1, s, memo);
return memo[i - 1, j - 1];
}
// Else make two recursive calls.
memo[i - 1, j - 1]
= Math.Max(findLongestRepeatingSubsequence(
i - 1, j, s, memo),
findLongestRepeatingSubsequence(
i, j - 1, s, memo));
return memo[i - 1, j - 1];
}
static int longestRepSubseq(string s) {
int n = s.Length;
int[, ] memo = new int[n, n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
memo[i, j] = -1;
}
}
return findLongestRepeatingSubsequence(n, n, s,
memo);
}
static void Main(string[] args) {
string s = "axxxy";
int res = longestRepSubseq(s);
Console.WriteLine(res);
}
}
function findLongestRepeatingSubsequence(i, j, s, memo)
{
// base case
if (i === 0 || j === 0)
return 0;
// If value is computed, return it.
if (memo[i - 1][j - 1] !== -1)
return memo[i - 1][j - 1];
// If characters match and their
// indices are different
if (s[i - 1] === s[j - 1] && i !== j) {
return memo[i - 1][j - 1]
= 1
+ findLongestRepeatingSubsequence(
i - 1, j - 1, s, memo);
}
// Else make two recursive calls.
return memo[i - 1][j - 1]
= Math.max(findLongestRepeatingSubsequence(
i - 1, j, s, memo),
findLongestRepeatingSubsequence(
i, j - 1, s, memo));
}
function longestRepSubseq(s)
{
const n = s.length;
const memo
= Array.from({length : n}, () => Array(n).fill(-1));
return findLongestRepeatingSubsequence(n, n, s, memo);
}
// Driver Code
const s = "axxxy";
const res = longestRepSubseq(s);
console.log(res);
Output
2
[Expected Approach - 1] Using Bottom-Up DP (Tabulation) - O(n ^ 2) Time and O(n ^ 2) Space
The approach is similar to the previous one. just instead of breaking down the problem recursively, we iteratively build up the solution by calculating in bottom-up manner. The idea is to create a 2-D array. Then fill the values using dp[i][j] = 1 + dp[i-1][j-1] if characters match and indices don't match. Otherwise, set dp[i][j] = max(dp[i-1][j], dp[i][j-1]).
Working of Approach:
- We use a 2D Dynamic Programming table dp, where dp[i, j] stores the length of the longest repeating subsequence using the first i and j characters.
- If s[i-1] == s[j-1] and i != j, we include the character and set dp[i, j] = 1 + dp[i-1, j-1].
- Otherwise, we take the maximum of excluding the current character from either side: max(dp[i-1, j], dp[i, j-1]).
- The condition i != j ensures that the same character position is not matched with itself.
- Finally, dp[n, n] contains the length of the Longest Repeating Subsequence.
#include <iostream>
#include <vector>
using namespace std;
int longestRepSubseq(string& s) {
int n = s.length();
vector<vector<int>> dp(n+1, vector<int>(n+1, 0));
for (int i=1; i<=n; i++) {
for (int j=1; j<=n; j++) {
// If char match and indices
// are different
if (s[i-1]==s[j-1] && i!=j) {
dp[i][j] = 1 + dp[i-1][j-1];
}
else {
dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
}
}
}
return dp[n][n];
}
int main() {
string s = "axxxy";
int res = longestRepSubseq(s);
cout << res;
return 0;
}
class GFG {
static int longestRepSubseq(String s) {
int n = s.length();
int[][] dp = new int[n + 1][n + 1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
// If char match and indices
// are different
if (s.charAt(i - 1) == s.charAt(j - 1) && i != j) {
dp[i][j] = 1 + dp[i - 1][j - 1];
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[n][n];
}
public static void main(String[] args) {
String s = "axxxy";
int res = longestRepSubseq(s);
System.out.println(res);
}
}
def longestRepSubseq(s):
n = len(s)
dp = [[0 for _ in range(n + 1)] for _ in range(n + 1)]
for i in range(1, n + 1):
for j in range(1, n + 1):
# If char match and indices
# are different
if s[i - 1] == s[j - 1] and i != j:
dp[i][j] = 1 + dp[i - 1][j - 1]
else:
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
return dp[n][n]
if __name__ == "__main__":
s = "axxxy"
res = longestRepSubseq(s)
print(res)
using System;
class GFG {
static int longestRepSubseq(string s) {
int n = s.Length;
int[,] dp = new int[n + 1, n + 1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
// If char match and indices
// are different
if (s[i - 1] == s[j - 1] && i != j) {
dp[i, j] = 1 + dp[i - 1, j - 1];
} else {
dp[i, j] = Math.Max(dp[i - 1, j], dp[i, j - 1]);
}
}
}
return dp[n, n];
}
static void Main(string[] args) {
string s = "axxxy";
int res = longestRepSubseq(s);
Console.WriteLine(res);
}
}
function longestRepSubseq(s) {
const n = s.length;
const dp = Array.from({ length: n + 1 }, () => Array(n + 1).fill(0));
for (let i = 1; i <= n; i++) {
for (let j = 1; j <= n; j++) {
// If char match and indices
// are different
if (s[i - 1] === s[j - 1] && i !== j) {
dp[i][j] = 1 + dp[i - 1][j - 1];
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[n][n];
}
//Driver Code
const s = "axxxy";
const res = longestRepSubseq(s);
console.log(res);
Output
2
[Expected Approach - 2] Using Space Optimized DP - O(n ^ 2) Time and O(n) Space
The idea is to store only the values required from the previous row. We can observe that for a given (i, j) its value is only dependent on (i-1, j-1) or (i-1, j) and (i, j-1).
Working of Approach:
- We use 1D Dynamic Programming to find the Longest Repeating Subsequence by comparing the string with itself.
- For every pair of indices (i, j), if s[i-1] == s[j-1] and i != j, we extend the subsequence using the diagonal value.
- Otherwise, we take the maximum of the left (curr[j-1]) and top (curr[j]) values.
- The match variable stores the previous diagonal value dp[i-1][j-1] to optimize space.
- The final value curr[n] gives the length of the Longest Repeating Subsequence.
#include <bits/stdc++.h>
using namespace std;
int longestRepSubseq(string &s) {
int n = s.length();
// Create a 1D array for the current row
vector<int> curr(n + 1, 0);
// Variable to store dp[i-1][j-1] for each (i, j)
// This helps to track the diagonal value from the previous iteration
int match = 0;
for (int i = 1; i <= n; i++) {
// Reset match to 0 for the new row
match = 0;
for (int j = 1; j <= n; j++) {
// Store the current cell value before updating
int tmp = curr[j];
// If characters match and indices are different
if (s[i - 1] == s[j - 1] && i != j) {
// Add 1 to the diagonal value
curr[j] = 1 + match;
}
else {
// Take the maximum value between left and top cells
curr[j] = max(curr[j], curr[j - 1]);
}
// Update match to the previous cell value
match = tmp;
}
}
return curr[n];
}
int main() {
string s = "axxxy";
int res = longestRepSubseq(s);
cout << res;
return 0;
}
class GFG {
static int longestRepSubseq(String s) {
// Get the length of the input string
int n = s.length();
// Create a 1D array to store the current row's
// values
int[] curr = new int[n + 1];
// Variable to store the value of dp[i-1][j-1]
// (diagonal element)
int match;
// Iterate over all characters of the string for row
// index
for (int i = 1; i <= n; i++) {
// Reset match for each new row
match = 0;
// Iterate over all characters of the string for
// column index
for (int j = 1; j <= n; j++) {
// Temporarily store the current cell value
// before updating it
int tmp = curr[j];
// If characters match and indices are
// different, update with diagonal value + 1
if (s.charAt(i - 1) == s.charAt(j - 1)
&& i != j) {
curr[j] = 1 + match;
}
else {
// Otherwise, take the maximum of the
// left and top cells
curr[j]
= Math.max(curr[j], curr[j - 1]);
}
// Update match to the previously stored
// value of curr[j]
match = tmp;
}
}
// Return the value in the last cell, which contains
// the length of the longest repeating subsequence
return curr[n];
}
public static void main(String[] args) {
String s = "axxxy";
int res = longestRepSubseq(s);
System.out.println(res);
}
}
def longestRepSubseq(s):
# Get the length of the input string
n = len(s)
# Create a 1D array to store the current row's values
curr = [0] * (n + 1)
# Variable to store the value of dp[i-1][j-1] (diagonal element)
match = 0
# Iterate over all characters of the string for row index
for i in range(1, n + 1):
# Reset match for each new row
match = 0
# Iterate over all characters of the string for column index
for j in range(1, n + 1):
# Temporarily store the current cell value before updating it
tmp = curr[j]
# If characters match and indices are different,
# update with diagonal value + 1
if s[i - 1] == s[j - 1] and i != j:
curr[j] = 1 + match
else:
# Otherwise, take the maximum of the left and top cells
curr[j] = max(curr[j], curr[j - 1])
# Update match to the previously stored value of curr[j]
match = tmp
# Return the value in the last cell, which contains the length of
# the longest repeating subsequence
return curr[n]
if __name__ == "__main__":
s = "axxxy"
res = longestRepSubseq(s)
print(res)
using System;
class GFG {
static int longestRepSubseq(string s) {
// Get the length of the input string
int n = s.Length;
// Create a 1D array to store the current row's
// values
int[] curr = new int[n + 1];
// Variable to store the value of dp[i-1][j-1]
// (diagonal element)
int match = 0;
// Iterate over all characters of the string for row
// index
for (int i = 1; i <= n; i++) {
// Reset match for each new row
match = 0;
// Iterate over all characters of the string for
// column index
for (int j = 1; j <= n; j++) {
// Temporarily store the current cell value
// before updating it
int tmp = curr[j];
// If characters match and indices are
// different, update with diagonal value + 1
if (s[i - 1] == s[j - 1] && i != j) {
curr[j] = 1 + match;
}
else {
// Otherwise, take the maximum of the
// left and top cells
curr[j]
= Math.Max(curr[j], curr[j - 1]);
}
// Update match to the previously stored
// value of curr[j]
match = tmp;
}
}
// Return the value in the last cell, which contains
// the length of the longest repeating subsequence
return curr[n];
}
static void Main(string[] args) {
string s = "axxxy";
int res = longestRepSubseq(s);
Console.WriteLine(res);
}
}
function longestRepSubseq(s)
{
let n = s.length;
// Create a 1D array for the current row
let curr = Array(n + 1).fill(0);
// Variable to store dp[i-1][j-1] for each (i, j)
// This helps to track the diagonal value from the
// previous iteration
let match = 0;
for (let i = 1; i <= n; i++) {
// Reset match to 0 for the new row
match = 0;
for (let j = 1; j <= n; j++) {
// Store the current cell value before updating
let tmp = curr[j];
// If characters match and indices are different
if (s[i - 1] === s[j - 1] && i !== j) {
// Add 1 to the diagonal value
curr[j] = 1 + match;
}
else {
// Take the maximum value between left and
// top cells
curr[j] = Math.max(curr[j], curr[j - 1]);
}
// Update match to the previous cell value
match = tmp;
}
}
return curr[n];
}
// Driver Code
let s = "axxxy";
let res = longestRepSubseq(s);
console.log(res);
Output
2