/* LongComSubseq.java ** An instance of this class can provide information about longest common ** subsequences of (any prefixes of) two specified character strings. In ** creating such an instance, the client provides the pair of strings of ** interest, X and Y. Let M and N be the lengths of X and Y, respectively. ** For m satisfying 0<=m<=M and n satisfying 0<=n<=N, let LLCS.m.n be the ** Length of the Longest Common Subsequences of X[0..m) and Y[0..n) ** (i.e., the prefixes of lengths m and n of X and Y). ** A "maximal (m,n)-matching" is a pair of mappings (f,g), ** f : [0..k) --> [0..m) and g : [0..k) --> [0..n), where k = LLCS.m.n, ** such that each of f and g is increasing and, for each i in [0..k), ** X.(f.i) = Y.(g.i). Thus, f describes a set of positions within X and ** g describes a set of positions within Y, such that erasing the characters ** in all other positions leaves you with a longest common subsequence (LCS) ** of X[0..m) and Y[0..n). (A "plain" (m,n)-matching (i.e., one that is not ** necessarily "maximal") has the same description, except that it could ** be that k < LLCS. ** ** Methods in this class are able to do the following: ** ** (1) Print a table showing, for all m<=M and n<=N, the value of LLCS.m.n. ** ** (2) Answer the question: For given m<=M and n<=N, what is LLCS.m.n? ** ** (3) Determine, for any m<=M and n<=N, an LCS of X[0..m) and Y[0..n). ** ** (4) Determine, for any m<=M and n<=N, a maximal (m,n)-matching. ** ** (5) Determine, for any m<=M and n<=N, the number of distinct ** maximal (m,n)-matchings. ** ** Author: R. McCloskey, 2015 (modified most recently in 2020) */ public class LongComSubseq { // instance variables // ------------------ private final String X, Y; // the Strings of interest private final int M, N; // their lengths (redundant data) private int[][] llcs; // 2-dimensional array storing values of the // LLCS function applied to X and Y. private int[][] nlcs; // stores the values of the NLCS function. // constructor (and supporing method) // ----------- /* Establishes the two given Strings as those that are the focus of ** this object. */ public LongComSubseq(String s1, String s2) { X = s1; Y = s2; M = X.length(); N = Y.length(); // Fill the llcs[][] array with LLCS function values. computeLLCS(); // Fill the nlcs[][] array with NLCS function values. computeNLCS(); } /* Constructs and fills llcs[][] so that, for all i in [0..M] ** and j in [0..N] (where M and N are the lengths of X and Y), ** llcs[i][j] = LLCS.i.j. Which is to say that, upon completion of ** this method's execution, the value of llcs[i][j] is the length of ** any longest common subsequence of X[0..i) and Y[0..j). */ private void computeLLCS() { llcs = new int[M+1][N+1]; // Fill row zero and column zero with zeros. for (int j=0; j != N+1; j++) { llcs[0][j] = 0; } for (int i=0; i != M+1; i++) { llcs[i][0] = 0; } int m=1, n=1; /* loop invariant: (A i,j | 0<=i maxSoFar) { maxSoFar = ary[i][j]; } } } return maxSoFar; } /* Reports whether the given two-dimensional array is a valid maximal ** (M,N)-matching. */ public boolean isValidMaxMatching(int[][] matching) { return isValidMaxMatching(matching, M, N); } /* Reports whether the given two-dimensional array is a valid maximal ** (m,n)-matching. */ public boolean isValidMaxMatching(int[][] matching, int m, int n) { return matching[0].length == llcs[m][n] && isValidMatching(matching, m, n); } /* Reports whether the given two-dimensional array is a valid (but not ** necessarily maximal) (M,N)-matching. */ public boolean isValidMatching(int[][] matching) { return isValidMatching(matching, M, N); } /* Reports whether the given two-dimensional array is a valid (but not ** necessarily maximal) (m,n)-matching, which is to say that it has two ** rows of the same length, the values in each row are increasing, and ** they describe a common subsequence of X[0..m) and Y[0..n). */ public boolean isValidMatching(int[][] matching, int m, int n) { if (matching.length != 2) { return false; } else { int[] f = matching[0]; int[] g = matching[1]; if (f.length != g.length) { return false; } else if (!isIncreasing(f)) { return false; } else if (!isIncreasing(g)) { return false; } else if (f[0] < 0 || f[f.length-1] >= m) { return false; } else if (g[0] < 0 || g[g.length-1] >= n) { return false; } else { // verify that f and g describe same string final int LEN = f.length; int i = 0; while (i != LEN && X.charAt(f[i]) == Y.charAt(g[i])) { i = i+1; } return i == LEN; } } } /* Prints both components in the specified matching. ** pre: mat is a valid matching */ public void printMatching(int[][] mat) { final int LEN = mat[0].length; for (int i=0; i != 2; i++) { for (int j=0; j != LEN; j++) { System.out.printf("%3d ", mat[i][j]); } System.out.println(); } } // private methods // --------------- /* Prints the specified string the specified # of times ** (used in printing the LLCS table). */ private void printString(String str, int k) { for (int j=0; j != k; j++) { System.out.print(str); } } /* Reports whether the values in the given array are increasing. */ private boolean isIncreasing(int[] a) { final int LEN = a.length; if (LEN <= 1) { return true; } else { int i = 1; // loop invariant: a[0..i) is increasing. while (i != a.length && a[i-1] < a[i]) { i = i+1; } return i == a.length; } } }