文本串:aabaabaaf
模式串:aabaaf
问题需求:需要找到文本串中是否存在模式串的匹配问题
前缀与后缀:对于字符串aabaaf,前缀是不包含最后一个字符的所有字串,后缀是不包含第一个字符的所有字串
前缀:a、aa、aab、aaba、aabaa
后缀:f、af、aaf、baaf、abaaf
前缀表:前缀表是用于找到字符串的所有字串最长相等前后缀的一个数组,其作用是用于进行字符串匹配。
例如:
a: 0 只有一个字符,为0
aa: 1 前缀为a 后缀为a 相等,最长相等前后缀为1
aab :0 前缀为a、a; 后缀为b、ab,最长相等前后缀长度为0
aaba: 1 前a、aa、aab;后a、ba、aba,最长相等前后缀长度为1
aabaa: 2 前a、aa、aab、aaba;后a、aa、baa、abaa,最长相等前后缀长度为2
aabaaf: 0 前a、aa、aab、aaba、aabaa;后f、af、aaf、baaf、abaaf,最长相等前后缀长度为0
因此可以得到前缀表,即next数组。next = [0, 1, 0, 1, 2, 0]
前缀表实现代码:
private int[] returnNext(String s){
int[] next = new int[s.length()];
//j指向前缀末尾
int j = 0;
//i指向后缀末尾
for(int i = 1; i < s.length(); i++){
//处理i与j位置值不等的情况,此时需要j不断回退,进入while循环
while(j > 0 && s.charAt(i) != s.charAt(j)){
j = next[j - 1];
}
//处理i与j位置值相等的情况,此时j需要前进,进行j++
if(s.charAt(i) == s.charAt(j)){
j++;
}
//更新next数组的值
next[i] = j;
}
return next;
}
题目链接
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1 。
分析:找到模式串needle的前缀表next数组,随后比较haystack 与needle数组,当二者相等时,继续向后比较,遇到不等时,根据next数组的值进行回退。
代码:
public int strStr(String haystack, String needle) {
int[] next = getNext(needle);
int start = -1;
int end = -1;
int j = 0;
for(int i = 0; i < haystack.length(); i++){
if(haystack.charAt(i) == needle.charAt(j)){
j++;
if(j < needle.length()){
continue;
}else{
end = i;
break;
}
}
while(j > 0 && haystack.charAt(i) != needle.charAt(j)){
j = next[j - 1];
}
if(haystack.charAt(i) == needle.charAt(j)){
j++;
}
if(j == needle.length() - 1 && needle.length() - 1 > 0){
end = i;
}
}
if(end != -1){
start = end - needle.length() + 1;
}
return start;
}
int[] getNext(String s){
int[] next = new int[s.length()];
int j = 0;
for(int i = 1; i < s.length(); i++){
while(j > 0 && s.charAt(i) != s.charAt(j)){
j = next[j - 1];
}
if(s.charAt(i) == s.charAt(j)){
j++;
}
next[i] = j;
}
return next;
}
因为需要找到模式串在文本串中的起始位置,所以将start和end都定义为 - 1,并且遍历最终如果end不为 - 1的话返回结果为:end - needle.length() - 1。
j是模式串needle的指针, i是文本串haystack的指针。进入for循环比较后,首先比较当前两个位置的值是否相等:
if(haystack.charAt(i) == needle.charAt(j)){
j++;
if(j < needle.length()){
continue;
}else{
end = i;
break;
}
}
这里需要判断,只有当j还没有遍历到needle的末尾时,继续遍历,否则证明已经遍历到模式串末尾了并且还相等,证明已经匹配结束,将end值更新为当前的i,并退出循环。
针对i与j位置不相等的情况,按照next数组进行回退,注意是一直回退直到相等
while(j > 0 && haystack.charAt(i) != needle.charAt(j)){
j = next[j - 1];
}
if(haystack.charAt(i) == needle.charAt(j)){
j++;
}
在while回退后紧跟的这个if判断的目的是,为了判断导致while退出循环的是否是已经在next数组中找到与当前i相等的在模式串中的位置,即排除j<0 的情况。此时因为已经找到了与当前i相等的j,对j进行更新,做j++。
最后当j遍历到needle末尾,退出循环,更新end值,排除单个字符的needle情况
if(j == needle.length() - 1 && needle.length() - 1 > 0){
end = i;
}
题目链接
分析:对于由重复子字符串构成的字符串:
abababab
next = [0, 0, 1, 2, 3, 4, 5, 6]
其最长相等前后缀长度为6,也就是说相等的最长前后缀都是:ababab,那么将原字符串长度与该最长相等前后缀长度相减,即得到重复字串ab。也就是说当len % (len - next[len - 1]) == 0时,该字符串由重复字串构成。
代码:
public boolean repeatedSubstringPattern(String s) {
if(s.length() <= 1){
return false;
}
int len = s.length();
int[] next = returnNextArray(s);
int max = 0;
return next[len - 1] > 0 && len % (len - next[len - 1]) == 0? true: false;
}
private int[] returnNextArray(String s){
int[] next = new int[s.length()];
int j = 0;
next[0] = 0;
for(int i = 1;i < next.length;i++){
while(j > 0 && s.charAt(i) != s.charAt(j)){
j = next[j - 1];
}
if(s.charAt(i) == s.charAt(j)){
j++;
}
next[i] = j;
}
return next;
}