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());
//上面错在多加了一个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;
}
与上面的前缀类似,只是一些简单的坐标要变
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[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
*
*动态规划的时候,此时是按子串的长度进行的
*/
/**
* 最长回文子串
* 有经典的线性时间的算法,记不住
*
* 还是用动态规划
*
* dp[i][j]表示x[i]到x[j]之间的字符串是不是回文, 0表示不是回文,1表示是回文
* j每次从i当前位置递增到len;而len从开始递增到最大长度
*
* dp[i][j]=dp[i+1][j-1] x[i]=x[j]
* dp[i][j]=0 x[i]!=x[j]
*
*
* 也是有点暴力的感觉,根据长度来判断,每次都从i=0,开始,一直到长度为最大长度
*
*/
public static String longestPalindromeSubString(String s){
if(s==null||s.length()==0) return "";
if(s.length()==1) return s;
int len=s.length();
int [][]dp=new int[len][len];
int maxLen=1,maxIndex=0;//起始的最大为1,下标为0
//长度为1
for(int i=0;i<len;i++){
dp[i][i]=1;//x[i]到x[i]的最大回文子串
}
for(int lenl=2;lenl<=len;lenl++){//按长度递增
for(int i=0;i<=len-lenl;i++){//左边的i
int j=i+lenl-1;
if(s.charAt(i)==s.charAt(j)){
dp[i][j]=1;
maxIndex=i;
maxLen=lenl;
}
}
}
return s.substring(maxIndex,maxIndex+maxLen);
}
5,最长公共子序列
- 先来一个返回子序列长度的版本
/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{
dp[i][j]=Math.max(dp[i-1][j], dp[i][j-1]);
}
}
}
return dp[len1][len2];
}
2,返回一个子序列版本的,代码大致差不多
public static String LCSubseq(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];
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{
dp[i][j]=Math.max(dp[i-1][j], dp[i][j-1]);
}
}
}
int max= dp[len1][len2];
System.out.println("最大长度为"+max);
StringBuilder sb=new StringBuilder();
//输出这个序列
while(len1>=1&&len2>=1){
if(s1.charAt(len1-1)==s2.charAt(len2-1)){
sb.append(s1.charAt(len1-1));
len1--;
len2--;
}else{//不等的时候
if(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{
dp[i][j]=Math.max(dp[i-1][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);
}
后面还有需要改进的和优化的,先到这把