String text = “Hello World”;
String pattern = “or”;
text.indexOf(pattern); // 7
text.indexOf(pattern); // -1
几个经典的串匹配算法:
-
蛮力(Brute Force)
-
KMP
-
Boyer-Moore
-
Karp-Rabin / Rabin-Karp
-
Sunday
下面用 tlen
代表文本串 text 的长度,plen
代表模式串 pattern 的长度;
==================================================================================
- 以字符为单位,从左到右移动模式串,直到匹配成功 ;
蛮力算法有 2 种常见实现思路:
/**
- 蛮力匹配
*/
public static int indexOf(String text, String pattern) {
if (text == null || pattern == null) return -1;
char[] textChars = text.toCharArray();
int tlen = textChars.length;
char[] patternChars = pattern.toCharArray();
int plen = patternChars.length;
if (tlen == 0 || plen == 0 || tlen < plen) return -1;
int pi = 0, ti = 0;
while (pi < plen && ti < tlen) {
if (textChars[ti] == patternChars[pi]) {
ti++;
pi++;
} else {
ti = ti - pi + 1;
// ti -= pi - 1;
pi = 0;
}
}
return pi == plen ? ti - pi : -1;
}
/**
- 蛮力匹配 - 改进
*/
public static int indexOf(String text, String pattern) {
if (text == null || pattern == null) return -1;
char[] textChars = text.toCharArray();
int tlen = textChars.length;
char[] patternChars = pattern.toCharArray();
int plen = patternChars.length;
if (tlen == 0 || plen == 0 || tlen < plen) return -1;
int pi = 0, ti = 0;
while (pi < plen && ti - pi <= tlen - plen) { // ti - pi <= tlen - plen 是关键
if (textChars[ti] == patternChars[pi]) {
ti++;
pi++;
} else {
ti = ti - pi + 1;
// ti -= pi - 1;
pi = 0;
}
}
return pi == plen ? ti - pi : -1;
}
/**
- 蛮力匹配2
*/
public static int indexOf(String text, String pattern) {
if (text == null || pattern == null) return -1;
char[] textChars = text.toCharArray();
int tlen = textChars.length;
char[] patternChars = pattern.toCharArray();
int plen = patternChars.length;
if (tlen == 0 || plen == 0 || tlen < plen) return -1;
// 如果模式串的头在 tlen - plen 后面, 必然会匹配失败
int tiMax = tlen - plen;
for (int ti = 0; ti <= tiMax; ti++) {
int pi = 0;
for (; pi < plen; pi++) {
if (textChars[ti + pi] != patternChars[pi]) break;
}
if (pi == plen) return ti;
}
return -1;
}
最好情况:
-
只需一轮比较就完全匹配成功,比较 m 次( m 是模式串的长度)
-
时间复杂度为 O(m)
最坏情况(字符集越大,出现概率越低):
-
执行了 n – m + 1 轮比较( n 是文本串的长度)
-
每轮都比较至模式串的末字符后失败( m – 1 次成功,1 次失败)
-
时间复杂度为 O(m ∗ (n − m + 1)),由于一般 m 远小于 n,所以为 O(mn)
======================================================================
KMP 是 Knuth–Morris–Pratt 的简称(取名自3位发明人的名字),于1977年发布
首先大概了解一下两者的差距:
蛮力算法:会经历很多次没有必要的比较。
KMP算法:充分利用了此前比较过的内容,可以很聪明地跳过一些不必要的比较位置。
KMP 会预先根据模式串的内容生成一张 next 表(一般是个数组):
例如,下图串匹配时, pi = 7 时 失配
- 根据失配的索引 7 查表,查到的元素索引为 3
next[pi] == 3
- 将模式串索引为 3 的元素移动到失配的位置
pi = next[pi]; // pi = 3
- 向右移动的距离为
p - next[pi]
再比如:pi = 3 时失配, next[3] = 0
,将 0 位置的元素移到失配处:pi = next[pi]
d、e 失配时,如果希望 pattern 能够一次性向右移动一大段距离,然后直接比较 d、c 字符
- 前提条件是 A 必须等于 B
所以 KMP 必须在失配字符 e 左边的子串中找出符合条件的 A、B,从而得知向右移动的距离
向右移动的距离:e左边子串的长度 – A的长度,等价于:e的索引 – c的索引,
且 c的索引 == next[e的索引],所以向右移动的距离:e的索引 – next[e的索引]
总结:
-
如果在 pi 位置失配,向右移动的距离是
pi – next[pi]
,所以next[pi]
越小,移动距离越大 -
next[pi]
是 pi 左边子串的真前缀后缀的最大公共子串长度
真前缀后缀的最大公共子串长度
如何求 真前缀后缀的最大公共子串长度:
构造 next 表
根据最大公共子串长度得到 next 表:
-1的精妙之处
为什么要将首字符设置为 - 1?
假设文本串是 AAAAABCDEF
,模式串是 AAAAB
:
已知 next[i] == n
;
① 如果 pattern.charAt(i)
== pattern.charAt(n)
- 那么
next[i + 1]
==n + 1
② 如果 pattern.charAt(i)
!= pattern.charAt(n)
-
已知
next[n]
==k
-
如果
pattern.charAt(i)
==pattern.charAt(k)
那么 next[i + 1]
== k + 1
- 如果
pattern.charAt(i)
!=pattern.charAt(k)
将 k 代入 n ,重复执行 ②
构造 next 表 代码实现:
private static int[] next(String pattern) {
char[] chars = pattern.toCharArray();
int[] next = new int [chars.length];
next[0] = -1;
int i = 0;
int n = -1;
int iMax = chars.length - 1;
while (i < iMax) {
if (n < 0 || chars[i] == chars[n]) {
next[++i] = ++n;
} else {
n = next[n];
}
}
return next;
}
假设文本串是 AAABAAAAB
,模式串是 AAAAB
-
如果
pattern[i]
!=d
,就让模式串滑动到next[i]
(也就是n)位置跟d
进行比较; -
如果
pattern[n]
!=d
,就让模式串滑动到next[n]
(也就是k)位置跟d
进行比较; -
如果
pattern[i]
==pattern[n]
,那么当i
位置失配时,
模式串最终必然会滑到 k
位置跟 d
进行比较,
所以 next[i]
直接存储 next[n]
(也就是k)即可;
next 表 的优化代码实现:
private static int[] next(String pattern) {
char[] chars = pattern.toCharArray();
int[] next = new int [chars.length];
next[0] = -1;
int i = 0;
int n = -1;
int iMax = chars.length - 1;
while (i < iMax) {
if (n < 0 || chars[i] == chars[n]) {
// 优化
++i;
++n;
if (chars[i] == chars[n]) {
next[i] = next[n];
} else {
next[i] = n;
}
} else {
n = next[n];
}
}
return next;
}
KMP 主逻辑:
-
最好时间复杂度:O(m)
-
最坏时间复杂度:O(n),不超过O(2n)
next 表的构造过程跟 KMP 主体逻辑类似:
- 时间复杂度:O(m)
KMP 整体:
-
最好时间复杂度:O(m)
-
最坏时间复杂度:O(n + m)
-
空间复杂度: O(m)
public class KMP {
public static int indexOf(String text, String pattern) {
// 检测数据合法性
if (text == null || pattern == null) return -1;
char[] textChars = text.toCharArray();
int tlen = textChars.length;
char[] patternChars = pattern.toCharArray();
int plen = patternChars.length;
if (tlen == 0 || plen == 0 || tlen < plen) return -1;
// next表
int[] next = next(pattern);
int pi = 0, ti = 0;
while (pi < plen && ti < tlen) {
// next表置-1的精妙之处, pi = -1 则 pi = 0, ti++ 相当于模式串后一一位
if (pi < 0 || textChars[ti] == patternChars[pi]) {
ti++;
pi++;
} else {
pi = next[pi];
}
}
return pi == plen ? ti - pi : -1;
}
/**
- next 表构造 - 优化
*/
private static int[] next(String pattern) {
char[] chars = pattern.toCharArray();
int[] next = new int [chars.length];
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
![img](https://i-blog.csdnimg.cn/blog_migrate/d47e3ff2636bba2c49fdd24c29af9d6d.jpeg)
总结
其他的内容都可以按照路线图里面整理出来的知识点逐一去熟悉,学习,消化,不建议你去看书学习,最好是多看一些视频,把不懂地方反复看,学习了一节视频内容第二天一定要去复习,并总结成思维导图,形成树状知识网络结构,方便日后复习。
这里还有一份很不错的《Java基础核心总结笔记》,特意跟大家分享出来
目录:
部分内容截图:
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
13548489619)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
![img](https://i-blog.csdnimg.cn/blog_migrate/d47e3ff2636bba2c49fdd24c29af9d6d.jpeg)
总结
其他的内容都可以按照路线图里面整理出来的知识点逐一去熟悉,学习,消化,不建议你去看书学习,最好是多看一些视频,把不懂地方反复看,学习了一节视频内容第二天一定要去复习,并总结成思维导图,形成树状知识网络结构,方便日后复习。
这里还有一份很不错的《Java基础核心总结笔记》,特意跟大家分享出来
目录:
[外链图片转存中…(img-8sKmJk3l-1713548489620)]
部分内容截图:
[外链图片转存中…(img-vrQU9fT3-1713548489621)]
[外链图片转存中…(img-qdvpDaWx-1713548489623)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!