题目:1910 || 28
给你两个字符串 s 和 part ,请你对 s 反复执行以下操作直到 所有 子字符串 part 都被删除: 找到 s 中 最左边 的子字符串 part ,并将它从 s 中删除。 请你返回从 s 中删除所有 part 子字符串以后得到的剩余字符串。 一个 子字符串 是一个字符串中连续的字符序列。
思路
这种字符串查找在java中的String类中有很多应用,用indexof查找到值,找到匹配的值在替换(也可以用截取等),不断递归就能得到。
代码实现
public static String removeOccurrences(String s, String part) {
int pos = s.indexOf(part);
while (pos != -1){
s = s.replaceFirst(part,""); //也可以用substring
pos = s.indexOf(part);
}
return s;
}
思考
该方法时间复杂度,以及性能能否有待提升
indexof:
/**
source:原字符串
sourceOffset:原字符串查找起始值
sourceCount:原字符串长度
target:匹配的字符串
targetOffset:配置字符串的起始值
targetCount:配置字符串的长度
fromIndex:起始值
*/
配合题目我把方法替换了数值,indexof首先是找到和s的首字符相等的位置,然后在和part每个字符逐个匹配,有一个不匹配的直接返回。
static int indexOf(String s,String part) {
char[] source = s.toCharArray();
char[] target = part.toCharArray();
if (0 == source.length) {
return (target.length == 0 ? 0 : -1);
}
if (part.length() == 0) {
return 0;
}
char first = target[0];
int max = source.length - target.length;
for (int i = 0; i <= max; i++) {
/* Look for first character. */
if (source[i] != first) {
while (++i <= max && source[i] != first);
}
/* Found first character, now look at the rest of v2 */
if (i <= max) {
int j = i + 1;
int end = j + target.length - 1;
for (int k = 1; j < end && source[j]
== target[k]; j++, k++);
if (j == end) {
/* Found whole string. */
return i;
}
}
}
return -1;
}
假设有字符串abcda去abcdeabsda匹配是否存在
第一次匹配:
后面的匹配过程:
在查找的过程,第一次匹配index匹配到4的时候发现不匹配了,第二次匹配j又回溯到了index:1,这种匹配明显重复查找了,能否找到一种方法在已匹配的结果中得到j去跳转的地方,而不是回溯到原来就已经匹配过的位置。所以就有kmp算法,由 Knuth-Morris-Pratt 这三个人发明的算法。
例子:假设有个字符串s为abcdabeabcdabda,有个字符串part需要去s中查找是否存在。
第一次匹配
KMP算法的想法是,设法利用这个已知信息,不要把"搜索位置"移回已经比较过的位置,继续把它向后移,这样就提高了效率.所以kmp会记录一份需要移动数组用于part移动的位置。后面介绍怎么算出来该数组值。
第二次匹配
已知e与D不匹配时,前面六个字符"ABCDAB"是匹配的。查表可知,最后一个匹配字符B对应的"部分匹配值"为2,因此按照下面的公式算出向后移动的位数:
移动位数 = 已匹配的字符数 - 对应的部分匹配值
因为 6 - 2 等于4,所以将搜索词向后移动4位。
第三次匹配
需要移动的位置为2-0 = 2
第四次匹配
数组j,k不相等,都向后移动一位
匹配k到最后一个字符,返回(j-k的值就是需要找到结果)。
代码实现
public static int kmp(String haystack, String needle) {
char[] hay = haystack.toCharArray();
char[] need = needle.toCharArray();
int i = 0,j=0;
int[] next = getNext(needle);
while(i<hay.length && j<need.length){
if(j == -1 || hay[i] == need[j]){
i++;
j++;
}else{
j = next[j];
}
}
if(needle.length() == j){
return i-j;
}else{
return -1;
}
}
现在重点就是如何获取next数组了
获取next数组
构建next数组的时候,刚开始的时候还是有些小疑问的?
question-1:next数组还和原字符串s有关吗?
解答:无关的,只和part字符串有关,因为kmp的思想是s移动的位置j不会在回溯到之前的位置,只会不断的移动part字符串上k的位置。
question-2:part字符串需要移动的位置是通过公式:移动位数 = 已匹配的字符数 - 对应的部分匹配值。那这个对应的匹配值怎么获取。
解答:对应的部分匹配值就是next数组,要获取next数组,我们可以看我们要跳转的位置,还是以前面的例子为例:
s和part已经匹配的位置是6位(abcdab),需要移动的位置是4位(abcd),abcdab中公共最大前缀后缀的长度为2。
前缀:除了最后一个字符,从第一个字符往后拼接的字符串 后缀:除了第一个字符,从最后一个字符往前拼接的字符串
也就是说,原模式串子串对应的各个前缀后缀的公共元素的最大长度表为:
我们通过上面的匹配的时候,发现k匹配最后一个字符d的时候,公共匹配数为2,也就是说计算的时候只是匹配(abcdab)的值,所以next的数组需要往后移动一位。
现在我们已经知道next数组通过前缀后缀的公共长度能获取出来了,但是代码怎么写呢?
字符串:abcdabd一开始p[j] != p[k],我们先看个简单点的字符:aaabac
当P[k] == P[j]时,
有next[j+1] == next[j] + 1=k+1。(next[j] == k)
当P[k] != P[j]时,
k应该通过回溯到坐标k之前的更短的子串来和j匹配,最笨的方法时用k之前的所有存在的子串来匹配,但考虑到next数组的含义,k对应的next[k]的表示k对应的字符之前的子串最大的相同前缀和后缀的长度,故直接将k左移到next[k]位置,继续匹配j
这个字符串看起来让人容易误解,假设s字符串为ababcd,part为abac.
下一次的匹配就变成了j和next[k]进行比较了
总结一下:
p[j] = p[k]时,next[j]=k,j,k向后移动
p[j] != p[k]时,k=next[k],然后接着匹配,一直回溯到最开始的位置
代码实现
public static int[] getNext(String need) {
char[] p = need.toCharArray();
int[] next = new int[p.length];
next[0] = -1;
int k = -1;
int j = 0;
while (j < p.length - 1) {
//p[k]表示前缀,p[j]表示后缀
if (k == -1 || p[k] == p[j]) {
//即当p[k] == p[j]时,next[j+1] == next[j] + 1=k+1
k++;
j++;
next[j] = k;
} else {
k = next[k];
}
}
return next;
}
完成代码:
public static int kmp(String haystack, String needle) {
char[] hay = haystack.toCharArray();
char[] need = needle.toCharArray();
int i = 0,j=0;
int[] next = getNext(needle);
while(i<hay.length && j<need.length){
if(j == -1 || hay[i] == need[j]){
i++;
j++;
}else{
j = next[j];
}
}
if(needle.length() == j){
return i-j;
}else{
return -1;
}
}
public static int[] getNext(String need) {
char[] p = need.toCharArray();
int[] next = new int[p.length];
next[0] = -1;
int k = -1;
int j = 0;
while (j < p.length - 1) {
//p[k]表示前缀,p[j]表示后缀
if (k == -1 || p[k] == p[j]) {
//即当p[k] == p[j]时,next[j+1] == next[j] + 1=k+1
k++;
j++;
next[j] = k;
} else {
k = next[k];
}
}
return next;
}
链接: leetcode-cn.com/problems/re…
参考:
1.
2.
www.cnblogs.com/zhangboy/p/…