codewars—Longest Common Subsequence

题目地址:https://www.codewars.com/kata/longest-common-subsequence/train/java

求两个字符串的最大公共序列

最长公共子串(Longest CommonSubstring)和最长公共子序列(LongestCommon Subsequence, LCS)的区别:子串(Substring)是串的一个连续的部分,子序列(Subsequence)则是从不改变序列的顺序,而从序列中去掉任意的元素而获得的新序列;更简略地说,前者(子串)的字符的位置必须连续,后者(子序列LCS)则不必。

    @Test
    public void exampleTests() {
        long startTime=System.nanoTime();   //获取开始时间  
        assertEquals("", Solution.lcs("a", "b"));
        assertEquals("abc", Solution.lcs("abcdef", "abc"));
        assertEquals("acf", Solution.lcs("abcdef", "acf"));
        assertEquals("12356", Solution.lcs("132535365", "123456789"));
//        assertEquals("BCBA", Solution.lcs("ABCBDAB", "BDCABA"));
        long endTime=System.nanoTime(); //获取结束时间  
        System.out.println("程序运行时间: "+(endTime-startTime)+"ns"); 
    }

方法一:
Brute-Force算法(暴力枚举):
X和Y的所有子序列都检查过后即可求出X和Y的最长公共子序列。X的一个子序列相应于下标序列{1, 2, …, m}的一个子序列,因此,X共有2m个不同子序列(Y亦如此,如为2^n),从而穷举搜索法需要指数时间(2^m * 2^n)。

class Solution {
    public static String lcs(String x, String y) {
        if(x.length()<y.length()){
            String z=x;
            x=y;
            y=z;
        }
        String maxstr="";
        for(int i=0;i<y.length();i++){
            StringBuilder strB=new StringBuilder();
            int a=x.indexOf(y.charAt(i));
            if(a==-1){
                continue;
            }else{
                strB.append(y.charAt(i));
                x=x.substring(a);
                for(int j=i+1;j<y.length();j++){
                    a=x.indexOf(y.charAt(j));
                    if(a==-1){
                        continue;
                    }else{
                        strB.append(y.charAt(j));
                        x=x.substring(a);
                    }
                }
            }
            if(strB.length()>maxstr.length()){
                maxstr=strB.toString();
            }
        }

        return maxstr;
    }
}

方法二:
LCS 最大公共序列算法
聪明的程序员想到了,一个用矩阵来查找的算法,就是把两个队列用整形矩阵表示, 相同的为1, 不同的为0, 然后求最大对角线,优化是优化了很多, 不过求最大对角线也不省心。

聪明的程序员再次优化了算法,就是相同的不是用1表示, 而是数字叠加,只需求最后一行前后数字查为1的列的对应字母(也可求最大对角线),时间复杂度也降到了 O(mn)+O(m+n)
比如求 x = “acf” and y = “abcdef”

      a  b  c  d  e  f
  [0, 0, 0, 0, 0, 0, 0]

a [0, 1, 1, 1, 1, 1, 1]

c [0, 1, 1, 2, 2, 2, 2]

f [0, 1, 1, 2, 2, 2, 3]
 class Solution {
    public static String lcs(String x, String y) {
        // your code here
                int m = x.length(), n = y.length();
        int[][] nums = new int[m + 1][n + 1];
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                nums[i][j] = nums[i - 1][j - 1] + (x.charAt(i - 1) == y.charAt(j - 1) ? 1 : 0);
                nums[i][j] = Math.max(nums[i][j], nums[i - 1][j]);
                nums[i][j] = Math.max(nums[i][j], nums[i][j - 1]);
            }
        }
        StringBuilder sb = new StringBuilder();
        for(int i = 1; i <= n; i++) {
            if (nums[m][i] - nums[m][i - 1] == 1) {
                sb.append(y.charAt(i - 1));
            }
        }
        return sb.toString();
    }
}

方法三:
动态规划算法
递归

记:
Xi=﹤x1,⋯,xi﹥ 即X序列的前 i 个字符 (1≤i≤m)(前缀)
Yj=﹤y1,⋯,yj﹥ 即Y序列的前 j 个字符 (1≤j≤n)(前缀)
假定
Z=﹤z1,⋯,zk﹥∈ LCS(X , Y)

  • 若xm=yn(最后一个字符相同),则不难用反证法证明:该字符必是X与Y的任一最长公共子序列Z(设长度为k)的最后一个字符,即有zk = xm = yn 且显然有Zk-1∈LCS(Xm-1 , Yn-1)即Z的前缀Zk-1是Xm-1与Yn-1的最长公共子序列。此时,问题化归成求Xm-1与Yn-1的LCS(LCS(X , Y)的长度等于LCS(Xm-1 , Yn-1)的长度加1)。
  • 若xm≠yn,则亦不难用反证法证明:要么Z∈LCS(Xm-1, Y),要么Z∈LCS(X , Yn-1)。由于zk≠xm与zk≠yn其中至少有一个必成立,若zk≠xm则有Z∈LCS(Xm-1 , Y),类似的,若zk≠yn 则有Z∈LCS(X , Yn-1)。此时,问题化归成求Xm-1与Y的LCS及X与Yn-1的LCS。LCS(X , Y)的长度为:max{LCS(Xm-1 , Y)的长度, LCS(X , Yn-1)的长度}。

另外两个序列的LCS中包含了两个序列的前缀的LCS,故问题具有最优子结构性质考虑用动态规划法。

最长公共子序列的结构有如下表示:

设序列X=< x1, x2, …, xm > 和 Y=< y1, y2, …, yn > 的一个最长公共子序列 Z=< z1, z2, …, zk >,则:

  1. 若xm=yn,则zk=xm=yn且Zk-1是Xm-1和Yn-1的最长公共子序列;
  2. 若xm≠yn且zk≠xm ,则Z是Xm-1和Y的最长公共子序列;
  3. 若xm≠yn且zk≠yn ,则Z是X和Yn-1的最长公共子序列。

其中Xm-1=< x1, x2, …, xm-1 >,Yn-1=< y1, y2, …, yn-1 >,Zk-1=< z1, z2, …, zk-1 >。

但是这个发现费时很高~

public class Solution {
    public static String lcs(String x, String y) {
       if (x.length() == 0 || y.length() == 0)
          return "";

       String xlast=x.substring(x.length()-1);
       String ylast=y.substring(y.length()-1);
       if (xlast.equals(ylast))
         return lcs( x.substring(0,x.length()-1), y.substring(0,y.length()-1) ) + ylast;

       String lcsA = lcs( x, y.substring(0,y.length()-1) );
       String lcsB = lcs( x.substring(0,x.length()-1), y );

       return (lcsA.length() > lcsB.length() ? lcsA : lcsB);
    }
}

最长递增子序列问题 Longest Increasing Subsequence (LIS)

设L=< a1,a2,…,an>是n个不同的实数的序列,L的递增子序列是这样一个子序列 Lin=< aK1,ak2,…,akm >,其中k1< k2 <…< km 且 aK1< ak2< …< akm。求这个子序列。

第一种算法:转化为LCS问题求解

设序列 X=< b1,b2,…,bn>是对序列 L=< a1,a2,…,an>按递增排好序的序列。那么显然X与L的最长公共子序列即为 L 的最长递增子序列。这样就把求最长递增子序列的问题转化为求最长公共子序列问题LCS了。

public class Main2 {
    public static void main(String[] args) {
        int[] x={2,3,7,1,4,9,1};
        List<Integer> listx=new ArrayList<Integer>();
        List<Integer> listy=new ArrayList<Integer>();
        for(int i=0;i<x.length;i++){
            listx.add(x[i]);
            listy.add(x[i]);
        }
        Collections.sort(listy);

        System.out.println(new Solution().LCS(listx,listy));
    }
}

class Solution {
    public static List<Integer> LCS(List x, List y) {
       List<Integer> resultlist=new ArrayList<Integer>();
       if (x.size() == 0 || y.size() == 0)
          return new ArrayList<Integer>();

       int xlast=(int) x.subList(x.size()-1,x.size()).get(0);
       int ylast=(int) y.subList(y.size()-1,y.size()).get(0);
       if (xlast==ylast){
          resultlist.addAll(LCS( x.subList(0,x.size()-1), y.subList(0,y.size()-1)) );
          resultlist.add(ylast);
          return resultlist;
       }

       List lcsA = LCS( x, y.subList(0,y.size()-1) );
       List lcsB = LCS( x.subList(0,x.size()-1), y );

       return (lcsA.size() > lcsB.size() ? lcsA : lcsB);
    }
}
第二种算法:动态规划

确定状态:以第 i 项结尾的最长递增子序列的长度为 f(i)
初始状态:以a[0]结尾的最长递增子序列是他本身,所有长度为1,f(0)=1
终止状态:max{ f(0),f(1),f(2)…f(n-1) }
决策:已知全部 j < x的 f(j)。如果 a[j] < a[i] ,显然可以把 a[i] 连在 a[j] 的后面
无后效性:f(i) 只与当前正在判断的 a[j] 相关
收益表示: f(i)=max{ f(j)| j<i 且 a[j]<a[i] } + 1

时间复杂度:枚举i ,枚举 j 找到最大满足条件的 j ,所以为 O(n2) O ( n 2 )
空间复杂度:存储每个 f(i) ,所以为 O(n) O ( n )

// 只求最长递增子序列的个数
public static void lis(int[] a) {
        int n = a.length;
        int[] f = new int[n];// 用于存放f(i)值;
        f[0] = 1;// 以第a[0]为末元素的最长递增子序列长度为1;
        int max = 0;// 记录最大的f[i]值

        for (int i = 1; i < n; i++)// 循环n-1次
        {
            f[i] = 1;// f[i]的最小值为1
            for (int j = 0; j < i; j++)// 循环i 次
            {
                if (a[j] < a[i] && f[j] >= f[i])
                    f[i] = f[j] + 1;// 更新f[i]的值。
            }
            if (f[i] > max) {
                max = f[i];
            }
        }

        System.out.println(max);
    }

需要输入最长递增子序列:
如果有多个,只需出入一个

对于每个 f(i) 记录得到他的决策 j , 即能与该位置数字构成最长递增子序列的上一个数字的位置,用数组 prePosition[n] 保存
用变量 maxIndex 记录最大的 f[i] 的位置
最后依次从 prePosition[n] 中向前找,即可得到 最大递增子序列

   //输入最长递增子序列
    public static void lis(int[] a) {
        int n = a.length;
        int[] f = new int[n];// 用于存放f(i)值;
        int[] prePosition = new int[n];// 记录每个位置上构成最长递增子序列的前驱的位置
        f[0] = 1;// 以第a1为末元素的最长递增子序列长度为1
        prePosition[0] = -1;// 位置0的前驱是尽头
        int maxIndex=-1;//记录最长递增子序列最后一个元素的位置

        int max = 0;// 记录最大的f[i]值

        for (int i = 1; i < n; i++)// 循环n-1次
        {
            f[i] = 1;// f[i]的最小值为1
            prePosition[i] = -1;// 默认前驱是尽头
            for (int j = 0; j < i; j++)// 循环i 次
            {
                if (a[j] < a[i] && f[j] >= f[i]) {
                    f[i] = f[j] + 1;// 更新f[i]的值
                    prePosition[i] = j;
                }
            }
            if (f[i] > max) {
                max = f[i];
                maxIndex=i;
            }
        }

        int[] subSequence = new int[max];// 存放最大递增子序列
        subSequence[max - 1] = a[maxIndex];// 从后往前放
        int pre = prePosition[maxIndex];
        for (int j = max - 2; j >= 0; j--) {
            subSequence[j] = a[pre];
            pre = prePosition[pre];
        }

        for (int k = 0; k < max; k++) {//输出
            System.out.print(subSequence[k]);
            if (k != max - 1) {
                System.out.print(" ");
            } else {
                System.out.println();
            }
        }
    }

另一种写法,以前面类似,用 ArrayList<ArrayList<Integer>> res 在循环中直接保存,以每个位置结尾的最大递增子序列,用变量 maxIndex 记录最大的 f[i] 的位置,最后 res.get(maxIndex) 即为所求。

class Solution {
    public static ArrayList<Integer> maxSubIncreaseArray(int[] a) {
        int n = a.length;
        int[] f = new int[n];// 该列表用于标记包括当前元素在内的前半部分的各个位置的最长递增子序列的长度
        ArrayList<ArrayList<Integer>> res = new ArrayList<ArrayList<Integer>>();// 记录每个位置的最长子序列
        ArrayList<Integer> tmp = new ArrayList<Integer>();// 记录当前的最长子序列
        int index = -1;// 用于标记当前元素递增子序列上一个元素的位置

        int maxIndex = 0;// 用于标记res中最长递增子序列的位置
        int max = Integer.MIN_VALUE;// 最长递增子序列的长度

        f[0] = 1;// 该列表用于标记包括当前元素在内的前半部分的各个位置的最长递增子序列的长度
        tmp.add(a[0]);
        res.add(tmp);

        for (int i = 1; i < n; i++) {
            index = -1;
            tmp = new ArrayList<Integer>();
            f[i] = 1;// f[i]的最小值为1
            for (int j = 0; j < i; j++) {
                if (a[j] < a[i] && f[j] >= f[i]) {
                    f[i] = f[j]+1;
                    index = j;
                }
            }
            if (f[i] > max) {//max 存当前最大递增序列的长度值
                max = f[i];
                maxIndex = i;
            }

            if (index > -1) {//有比他小的数
                tmp.addAll(res.get(index));//加上前一个位置的最长递增子序列
            }

            tmp.add(a[i]);//加上i 这个位置的数

            res.add(tmp);//加到res中
        }

        return res.get(maxIndex);
    }
}
### 回答1: 最长公共子序列Longest Common Subsequence)指的是在两个序列中找到最长的公共子序列,这个公共子序列可以不连续,但是需要保持相对顺序不变。例如,对于序列ABCD和ACDFG,它们的最长公共子序列是ACD。 ### 回答2: 最长公共子序列Longest Common Subsequence,简称LCS)是指在给定多个序列中,找到最长的一个子序列,该子序列同时出现在这些序列中,并且其他元素的相对顺序保持一致。 举个例子,假设有两个序列A和B,A为[1, 2, 3, 4, 5],B为[2, 4, 5, 6]。它们的一个最长公共子序列是[2, 4, 5],该子序列同时存在于A和B中。 求解LCS的问题可以用动态规划的方法来解决。我们可以构建一个二维数组dp,其中dp[i][j]表示序列A的前i个元素和序列B的前j个元素的LCS长度。那么dp[i][j]可以通过以下方式得到: 1. 如果A[i]等于B[j],则dp[i][j]等于dp[i-1][j-1] + 1; 2. 如果A[i]不等于B[j],则dp[i][j]等于max(dp[i-1][j], dp[i][j-1])。 通过填充整个dp数组,最终可以得到序列A和序列B的LCS长度。要找到具体的LCS序列,则可以通过反向遍历dp数组进行构建。 LCS问题在字符串处理、DNA序列匹配、版本控制等领域都有广泛的应用。其时间复杂度为O(m*n),其中m和n分别为序列A和序列B的长度。 ### 回答3: 最长公共子序列Longest Common Subsequence)是一个经典的计算机科学问题。给定两个序列S和T,我们要找出它们之间最长的公共子序列。 子序列是从给定序列中按顺序选择几个元素而组成的序列。而公共子序列指的是同时是序列S和T的子序列的序列。 为了解决这个问题,可以使用动态规划的方法。我们可以定义一个二维数组dp,其中dp[i][j]表示序列S的前i个元素和序列T的前j个元素之间的最长公共子序列的长度。 接下来,我们可以使用以下递推关系来填充dp数组: 如果S[i]等于T[j],则dp[i][j] = dp[i-1][j-1] + 1; 如果S[i]不等于T[j],则dp[i][j] = max(dp[i-1][j], dp[i][j-1])。 最后,我们可以通过查看dp[S.length()][T.length()]来得到最长公共子序列的长度。 此外,我们也可以用回溯法来还原最长公共子序列本身。我们可以从dp[S.length()][T.length()]开始,如果S[i]等于T[j],则将S[i]添加到结果序列中,并向左上方移动,即i = i-1,j = j-1。如果S[i]不等于T[j],则根据dp数组的值选择向上(i = i-1)或向左(j = j-1)移动。 总之,最长公共子序列问题是一个经典的计算机科学问题,可以使用动态规划的方法解决。我们可以通过构建二维dp数组来计算最长公共子序列的长度,并可以使用回溯法来还原它本身。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值