滴血总结(java版):最长公共子序列(子串)、最长公共回文子序列(子串)、最长公共前缀(后缀)

1,最长公共前缀问题

有点类似冒泡算法,每次都要找最小的串的长度,然后进行截取,代码如下

public String longestCommonPrefix(String[] strs) {
         if(strs.length==0) return "";
         String s=strs[0];
          for (int i = 1; i < strs.length; i++) {
               if(s.length()==0||strs[i].length()==0) return "";
               int len=Math.min(s.length(), strs[i].length());    
               /*
               int count=0;//临时变量,记录比较时候到哪一位就停止
               for ( int j = 0; j <len; j++) {
                    //这里不能是==
                    if (s.charAt(j)!=strs[i].charAt(j))
                          {count=j;break;}               
               }
               s=s.substring(0, count);*/             
    //上面错在多加了一个count,但是在实际的时候当"a""b的时候,count为0 截取的子串还是“a”
               int j;
               for (  j = 0; j <len; j++) {
                    //这里不能是==
                    if (s.charAt(j)!=strs[i].charAt(j))
                         break;
               }
               s=s.substring(0, j);//0到j-1的位置
          }
          return s;
    }


2,最长公共后缀

与上面的前缀类似,只是一些简单的坐标要变

  public static String longestCommonLastfix(String[] strs) {
             if(strs. length==0) return "";
             //s总是暂时放求的的当前的值
             String s=strs[0];
             for ( int i = 1; i < strs. length; i++) {
                      if(strs[i].length()==0||s.length()==0)
                            return "";
                      int len1=strs[i].length();
                      int len2= s.length();
                   int len=Math. min(len1,len2);
                   int j;
                   for ( j=0;j<len; j++) {
                           if(s.charAt(len2-1-j)!=strs[i].charAt(len1-1-j))
                                 break;
                     }
                     s=s.substring(len2-j);
                }           
            return s;
         }


3,最长公共子串(返回子串长度或序列)

关于子串与子序列的问题,不想赘述了,分不清的话自己百度

思路:

子串是连续的,考虑动态规划的方法进行计算
dp[i][j]表示X[0-i]与Y[0-j]的最长公共子串的长度(包含x[i],y[j]的时候)
x[i]与y[j]来说要么与之前的公共子串构成新的公共子串;或者是补构成公共子串
1,x[i]=y[j],最边上的两个char相同,dp[i][j]=dp[i-1][j-1]+1
2,x[i]!=y[j],dp[i][j]=0
最大的值就是所有dp[i][j]中的最大值
// 动态规划问题
      /*
      * dp[i][j]表示X[0-i]与Y[0-j]的最长公共子串的长度
      * x[i]与y[j]来说要么与之前的公共子串构成新的公共子串;或者是补构成公共子串
      * 1,x[i]=y[j],最边上的两个char相同, dp[i][j]=dp [i-1][j-1]+1
      * 2,x[i]!=y[j], dp[i][j]=0,因为若不相等,后面包含此串的串公共长度肯定在这后面开始算
      */

   

 public static int longestCommonSubString(String s1,String s2) {
       int len1=s1.length(),len2=s2.length();
       if(len1==0||len2==0) return 0;
        //初始化的时候数组为0,所以后面只需要判断不为0的一些情况
       int[][] dp=new int[len1][len2];
       int max=0;
       for (int i = 0; i < len1; i++) {
            for ( int j = 0; j < len2; j++) {
                 if(s1.charAt(i)==s2.charAt(j)){
                      //i或j中有一个为0,情况特殊
                      if(i==0||j==0){
                           dp[i][j]=1;
                     }
                      else if(i!=0&&j!=0)     dp[i][j]=dp[i-1][j-1]+1;
                      if(dp[i][j]>max){
                           max=dp[i][j];
                      //可以知道最大的起始索引下标为i+1-max
                     }
                }
           }
     }          
            return max;
     }
在求最大长度的时候,顺便也把最大子串的起始坐标也知道了i+1-max,所以不论是返回子串还是子串的长度都不怕怕


4,最长回文子串

最长回文子串除了之前的必须是连续的子串外,还要求是回文
最长回文子串算法除了暴力方法外,还有动态规划算法(DP)和著名的Manacher算法,最优的时间复杂度为O(N)
首先要了解一下Manacher算法,也就是在所有的串串中间加一些特殊的分隔符,这样就简单话了奇数回文和偶数回文,不懂这个算法的可以去百度下

这里先给出我自己实现的算法,看不懂的慢慢看

/*
 * str_s = a b a a b a; 
   Index = 0 1 2 3 4 5 6 7 8 9 10 11 12 
   str[] = # a # b # a # a # b #  a #; 
   P[]   = 0 0 0 2 0 1 5 1 0 3 0  1 0; 
 */
     //最长公共子串,manacher 算法
      public static String longestPalindrome(String s) {
            if(s.length()==0||s== null) return "";
            if(s.length()==1) return s;
            int len=s.length();
             char[] ch= new char[2*len+1];
             int[] p= new int [2*len+1];
             //max是坐标id影响的最远的index,不是最大距离
             int max=0;
             //id表示的是影响到最远id的index,但不一定是最大的,所以后面还要寻找
             int id=0; //对称中心
             //添加特殊字符,一般是"#"
            for ( int i = 0; i < s.length(); i++) {
                ch[2*i+1]=s.charAt(i);
                ch[2*i]= '#';
           }
          ch[2*len]= '#';
         
            //进行判断
            for ( int i = 1; i < ch. length; i++) {
             //先把能进行扩展的p[i]值找到
                 if(max>i)
                     p[i]=Math. min(p[2*id-i], max-i);
                
                 //对i点进行扩展
                 while ((1+i+p[i])<ch.length&&(i-p[i]-1)>0&&(ch[1+i+p[i]]==ch[i-p[i]-1])) {
                     p[i]++;
                }
                
                 if(p[i]+i>max){
                     max=p[i]+i;
                     id=i;
                }
           }
     
           //寻找最大值
            max=0;
            id=0;
            for ( int i = 0; i < p. length; i++) {
                 if(max<p[i]){
                     max=p[i];
                     id=i;
                }
                     
           }
            //最大长度为原来的max+1
            StringBuffer sb= new StringBuffer();
            for ( int i = id-max; i <=id+max; i++) {
                 if(ch[i]!= '#') sb.append(ch[i]);
           }
            return sb.toString();
         }


求解回文子串法二:DP,动态规划

//动态规划求最长回文子串
       /*
      * dp[i][j]表示从字符串i到j之间的字符串是否是回文串, dp[i][j]=0,i到j不是回文, dp[i][j]=1,i到j是回文
       * dp[i][j]=dp [i+1][j-1]  s[i]=s[j]
       * dp[i][j]=0 s[i]!=s[j] i-j的子串不是回文子串,不去理会,初试的时候就是0
       *
       *动态规划的时候,此时是按子串的长度进行的
       */

     public static String longestPalindrome1(String s) {
            if(s.length()==0||s== null) return "";
            if(s.length()==1) return s;
           
            int maxLen=1,maxIndex=0;
            int len=s.length();
            int[][] dp= new int[len][len];
        
            //长度为1的时候
            for ( int i = 0; i < len; i++) {
                dp[i][i]=1;
           }
            //长度为2
            for ( int i = 0; i < len-1; i++) {
                 if(s.charAt(i)==s.charAt(i+1))
                     dp[i][i+1]=1;
                maxIndex=i;
                maxLen=2;
           }
            //长度大于1的时候
            for ( int len1=3 ; len1<=len ; len1++) {
                 for ( int i = 0; i <=len-len1; i++) {
                      int j=i+len1-1;
                      //dp[i+1][j-1]!=0判断不可少
                      if(s.charAt(i)==s.charAt(j)&&dp[i+1][j-1]!=0)
                {dp[i][j]=1;
                     maxIndex=i;
                     maxLen=len1;
                }
                }
           }          
           
            return s.substring(maxIndex,maxIndex+maxLen);
      }


5,最长公共子序列

  1.    先来一个返回子序列长度的版本  
/DP问题,动态规划一下 dp[i][j]表示x长度为i,y长度为j时候的最长公共子序列的长度
/*
 * 初始时候 dp[i][j]=0,
 * dp[i][j]=0,如果i或者j为0
 * dp[i][j]=dp[i-1][j-1]+1,如果前面的x[i-1]=y[j-1]
 * dp[i][j]=max{dp[i-1][j], dp[i][j-1]},当x[i-1]=y[j-1]
 * 这有点类似 leetcode最大抢劫问题,最大的就是 dp[len -1][len-1]的最大,但是这个是从长度为0开始,不是从下标为0    开始,
 */
    
public static int LCSubseq(String s1,String s2){
            if(s1.length()==0||s2.length()==0) return 0;
            int len1=s1.length();
            int len2=s2.length();
            int[][] dp= new int[len1+1][len2+1];
                     
            for ( int i = 1; i <=len1; i++) {
                 for ( int j = 1; j <=len2; j++) {
                
                   if(s1.charAt(i-1)==s2.charAt(j-1))
                        dp[i][j]=dp[i-1][j-1]+1;
                   else{
                        if(dp[i-1][j]>=dp[i][j-1])
                            dp[i][j]=dp[i-1][j];
                        else
                              dp[i][j]=dp[i][j-1];                
                   }   
                }
           }
            return dp[len1][len2];
     }
  2,返回一个子序列版本的,代码大致差不多
   
//最长公共子序列,输出序列
            public static String LCSubseq1(String s1,String s2){
                 if(s1.length()==0||s2.length()==0) return "";
                 int len1=s1.length();
                 int len2=s2.length();
                 int[][] dp= new int[len1+1][len2+1];
                StringBuffer sb= new StringBuffer();  
                     
                 for ( int i = 1; i <=len1; i++) {
                      for ( int j = 1; j <=len2; j++) {
                     
                        if(s1.charAt(i-1)==s2.charAt(j-1))
                              {dp[i][j]=dp[i-1][j-1]+1;
                          }
                        else{
                              if(dp[i-1][j]>=dp[i][j-1])
                                  dp[i][j]=dp[i-1][j];
                              else
                                   dp[i][j]=dp[i][j-1];                 
                        }   
                     }
                }
                 int max=dp[len1][len2];
                System. out.println( "最大长度为:" +max);
                 //随便输出一个序列
                
                 while(len1>=1&&len2>=1){
                      if(s1.charAt(len1-1)==s2.charAt(len2-1)&&dp[len1][len2]==dp[len1-1][len2-1]+1){
                           sb.append(s1.charAt(len1-1));
                           len1--;
                           len2--;
                     } else if(s1.charAt(len1-1)!=s2.charAt(len2-1)&&dp[len1-1][len2]>=dp[len1][len2-1]){
                           len1--;
                     } else{
                           len2--;
                     }
                }
                 return sb.reverse().toString();
           }   
很明显,这里的动态规划是根据序列长度来规划的,下面来一个根据坐标动态规划的
<pre name="code" class="java">//最长公共子序列,输出长度就行
		public static int LCSubseq1(String s1,String s2){
			if(s1.length()==0||s2.length()==0) return 0;
			int len1=s1.length();
			int len2=s2.length();
			int[][] dp=new int[len1][len2];
			StringBuffer sb=new StringBuffer();	
				
			//初始化
			for (int i = 0; i < len1; i++) {
				if(s1.charAt(i)==s2.charAt(0))
					dp[i][0]=1;
			}
			//初始化
				for (int i = 0; i < len2; i++) {
						if(s1.charAt(0)==s2.charAt(i))
							dp[0][i]=1;
			}
				
			for (int i = 1; i <len1; i++) {
				for (int j = 1; j <len2; j++) {
				
				   if(s1.charAt(i)==s2.charAt(j))
					   {dp[i][j]=dp[i-1][j-1]+1;
				     }
				   else{
					   if(dp[i-1][j]>=dp[i][j-1])
					       dp[i][j]=dp[i-1][j];
					   else
						   dp[i][j]=dp[i][j-1];			   
				   }	   
				}
			}
			int max=dp[len1-1][len2-1];
			System.out.println("最大长度为:"+max);
			/*//随便输出一个长度,有点bug,输不出String,以后再来改
			int i=len1-1,j=len2-1;
			while(i>=1&&j>=1){
				if(s1.charAt(i)==s2.charAt(j)&&dp[i][j]==dp[i-1][j-1]+1){
					sb.append(s1.charAt(i));
					i--;
					j--;
				}else if(s1.charAt(i)!=s2.charAt(j)&&dp[i-1][j]>=dp[i][j-1]){
					i--;
				}else{
					j--;
				}
			}
		//有点Bug,	
			if(i==0&&j==0){
				if(s1.charAt(i)==s2.charAt(0))
					sb.append(s1.charAt(0));
			}else if(i>j){
				while(i>=0)
					if(s1.charAt(i--)==s2.charAt(0))
						sb.append(s2.charAt(0));
			}else if(i<j){
				while(j>=0)
					if(s1.charAt(0)==s2.charAt(j--))
						sb.append(s1.charAt(0));
			}
			return sb.reverse().toString();*/
			return max;
		}


 
  

6,最长回文序列
1,递归的方式求解,返回子序列
  //最长回文子序列
             //递归
       /*
      * dp[i][j]表示坐标i到j之间的最长回文长度
      * 当x[i]=x[j], dp[i][j]=dp [i+1][j-1]+2;
      * 当x[i]!=x[j], dp[i][j]=max{dp [i+1][j],dp[i][j-1]}
      *
      * 初试的时候 dp[i][j]=dp [0][len-1]
      */  
 public static String LonPaliSubse(String s){
                int maxLength= Lps(s, 0, s.length()-1);
                   System. out.println( "最大回文子序列的长度为:" +maxLength);
                   //寻找最长子序列,并输出
                  
                   return null;
              }
  public static int Lps(String s, int begin, int last){
      if(begin==last)
            return 1;
      if(begin>last) return 0;
      if(s.charAt(begin)==s.charAt(last))
            return Lps(s, begin+1, last-1)+2;
      return Math. max(Lps(s, begin, last-1), Lps(s, begin+1,  last));
  }

2,动态规划,返回最大的回文序列长度
//DP规划
   /*
   * 此时的动态规划是从序列长度为1逐次递增进行动态的
   * dp[i][j] 从位置i到j之间的序列
   */
 
public static int LonPaliSubse1(String s){
       if(s.length()==0||s== null) return 0;
       int len=s.length();
       int[][] dp= new int[len][len];
       int max=0;
       //序列长度为1
       for ( int i = 0; i <len; i++)  dp[i][i]=1;
      
       //长度大于等于i+1的序列 dp[j][j+i]
       for ( int i = 1; i < len; i++) {
            for ( int j = 0; j+i<len; j++) {
                   //如果首位相同
                 if(s.charAt(j)==s.charAt(j+i))
                     max=dp[j+1][j+i-1]+2;
                 else max=Math. max(dp[j+1][j+i], dp[j][j+i-1]);
                
                dp[j][j+i]=max;
           }
     }
       return dp[0][len-1];
  }

3,构造将原字符串逆转,这样求回文子串,就是求这两个字符串的最大公共子序列,这根据上面求公共序列的情况就可以求了
public static String LonPaliSubse2(String s){
       if(s.length()==0||s== null) return "";
       StringBuffer sb= new StringBuffer(s);
       String s1=sb.reverse().toString(); 
       return LCSubseq2(s1, s);
  }
后面还有需要改进的和优化的,先到这把







你可能感兴趣的:(滴血总结(java版):最长公共子序列(子串)、最长公共回文子序列(子串)、最长公共前缀(后缀))