1.Manacher 算法
原理介绍:Manacher算法相对于KMP算法来说,用途还是比较浅的,该算法只适用于求字符串中最长回文串的长度以及回文串的数组。
-
首先对原字符串进行改造
原因:因为一般的字符串有可能是奇数也有可能是偶数,所以我们不得不去分类考虑这些情况(奇数偶数),但是我们对它进行改造,把它变为奇数位的一个字符串
abab—>#a#b#a#b# 4-->9
ababa—>#a#b#a#b#a# 5-->11
-
在我们改造后的数组中存在以下两大种情况
pArr[i] = R > i ? Math.min(pArr[2 * C - i], R - i) : 1;
-
i>R
-
i<R
-
Manacher算法的具体流程就是先匹配 -> 通过判断i与R的关系进行不同的分支操作 -> 继续遍历直到遍历完整个字符串
//求解回文串的最大长度
//Manacher 算法 马拉车算法 将奇偶数的字符串通过操作转换为奇数字符串进行统一操作
public int longestPalindrome(String s) {
//对原字符串记性判断
if (s == null || s.length() == 0) {
return 0;
}
//如果该字符串原本就是一个字符,直接返回原字符串
if (s.length() == 1) {
return 0;
}
//通过该方法去得到一个经过改变的字符数组
char[] str = manacherString(s);
//获取到长度
int n = str.length;
int[] pArr = new int[n];//回文半径数组
int C = -1;//中心
int R = -1;//回文串右边界再往右一个位置,右边的有限区域是在R-1的位置上
int center=0;
int max = Integer.MIN_VALUE;//扩出来的最大值
//对改变后的字符数组每一个元素进行求解回文半径
for (int i = 0; i < n; i++) {
//相当于对回文半径数组进行填充
pArr[i] = R > i ? Math.min(pArr[2 * C - i], R - i) : 1;
//找到再继续向两边进行扩展
while(i+pArr[i]<n&&i-pArr[i]>=0){
if(str[i+pArr[i]]==str[i-pArr[i]]){
pArr[i]++;
}else{
break;
}
}
//进行更新
if(i+pArr[i]>R){
R=i+pArr[i];
C=i;
}
if(pArr[i]>max){
center=i;
max=pArr[i];
}
}
return max-1;
}
//将原字符串进行特殊的改变
char[] manacherString(String s) {
//aba--->*a*b*a*
//声明一个长度2*s.length+1的字符数组
char[] str = new char[2 * s.length() + 1];
//对新创建的数组进行填充
int index = 1;
//第一种方法对新创建的数组进行遍历
// for (int i = 0; i < str.length; i++) {
// //根据我们观察,奇数位置上的位置都是我们添加的新字符
// str[i] = (i & 1) == 0 ? '*' : s.charAt(index++);
// }
//第二种方法对原字符串进行遍历
str[0]='*';
for (int i = 0; i < s.length(); i++) {
str[index++] = s.charAt(i);
str[index++] = '*';
}
return str;
}
//求得最长回文子串
StringBuilder sb = new StringBuilder();
//如果该中心位置不是为特殊字符,直接添加到StringBuilding中
if (str[center] != '*') {
sb.append(str[center]);
}
//往两边进行扩展
int left = center - 1;
int right = center + 1;
//如果两边是相同的字符,则同时左-- 右++; 相当于就是为了还原改变后的数组 在原数组中求得最长回文子串
while (left >= 0 && right <n && str[left] == str[right]) {
if (str[left] != '*') {
sb.insert(0, str[left]);
sb.append(str[right]);
}
left--;
right++;
}
return sb.toString();
2.KMP算法(字符串检索的算法)
原理:
不用进行重复比较,直接找到最长的前缀字符,在这之前我们首先先要求得模式串中next数组
private static int[] computeLPSArray(String pattern) {
int[] next = new int[pattern.length()];
next[0]=-1;
next[1]=0;
int i=2;//提前走了一步
//next数组中的元素,记录前缀最长匹配长度的索引
int k=0;//前一项的k
for (int j =2; j <pattern.length(); j++) {
if(k==-1||pattern.charAt(j-1)==pattern.charAt(k)){
next[i]=k+1;
k++;
i++;
}else{
k=next[k];
}
}
return next;
}
方法一:
public class KMPAlgorithm {
public static int kmp(String text, String pattern) {
int[] lps = computeLPSArray(pattern); // 计算模式字符串的LPS数组
int i = 0; // text中的索引
int j = 0; // pattern中的索引
while (i < text.length()) {
if (text.charAt(i) == pattern.charAt(j)) { // 当前字符匹配成功
i++; // 继续比较下一个字符
j++;
if (j == pattern.length()) {
return i - j; // 找到完全匹配,返回匹配的起始位置
}
} else {
if (j != 0) { // 如果pattern的前缀中存在部分匹配
j = lps[j - 1]; // 根据LPS数组进行跳转
} else {
i++; // 没有匹配时,移动text的指针
}
}
}
return -1; // 未找到匹配
}
private static int[] computeLPSArray(String pattern) {
int[] lps = new int[pattern.length()]; // 初始化LPS数组
int len = 0; // 前缀中的最长公共长度
int i = 1;
while (i < pattern.length()) {
if (pattern.charAt(i) == pattern.charAt(len)) { // 当前字符匹配成功
len++; // 前缀长度加1
lps[i] = len; // 更新LPS数组中对应位置的值
i++; // 继续比较下一个字符
} else {
if (len != 0) { // 前缀中存在部分匹配
len = lps[len - 1]; // 根据LPS数组进行跳转
} else {
lps[i] = 0; // 没有匹配时,LPS数组对应位置为0
i++; // 继续比较下一个字符
}
}
}
return lps; // 返回计算得到的LPS数组
}
public static void main(String[] args) {
String text = "ABABDABACDABABCABAB"; // 待搜索的文本
String pattern = "ABABCABAB"; // 要搜索的模式
int index = kmp(text, pattern); // 调用KMP算法进行匹配
if (index != -1) {
System.out.println("匹配发生在索引 " + index); // 输出匹配的起始位置
} else {
System.out.println("未找到匹配");
}
}
}
方法二:
class KMPAlgorithm {
public static int kmp(String text, String pattern, int pos) {
//对入参进行判断
//pos是对主串开始匹配的索引位置
if (text == null || pattern == null) return -1;
if (text.length() == 0 || pattern.length() == 0) return -1;
int textLen = text.length();
int patternLen = pattern.length();
if (pos < 0 || pos >= textLen) return -1;
int[] next = computeLPSArray(pattern);
//主串开始的索引
int i = pos;
//模式串匹配的索引
int j = 0;
while (i < textLen && j < patternLen) {
if ( j == -1||text.charAt(i) == pattern.charAt(j) ) {
i++;
j++;
} else {
j = next[j];
}
}
if (j >= patternLen) {
return i - j;
}
return -1;
}
private static int[] computeLPSArray(String pattern) {
int m = pattern.length();
int[] next = new int[m];
//通常next数组第一个就是为-1
next[0] = -1;
int i = 0, k = -1;
while (i < m - 1) {
//i代表的还是0~i-1所具有的最长前缀长度
if (k == -1 || pattern.charAt(i) == pattern.charAt(k)) {
i++;
k++;
next[i] = k;
} else {
//等于
k = next[k];
}
}
return next;
}
public static void main(String[] args) {
String text = "ABABDABACDABABCABAB"; // 待搜索的文本
String pattern = "ABABCABAB"; // 要搜索的模式
int index = kmp(text, pattern,0); // 调用KMP算法进行匹配
if (index != -1) {
System.out.println("匹配发生在索引 " + index); // 输出匹配的起始位置
} else {
System.out.println("未找到匹配");
}
}