字符串匹配小结
声明
文章均为本人技术笔记,转载请注明出处:
[1] https://segmentfault.com/u/yzwall
[2] blog.csdn.net/j_dark/
字符串匹配问题
问题描述:对于一个给定的 source 字符串和一个target字符串,你应该在source字符串中找出 target 字符串出现的第一个位置(从0开始)。如果不存在,则返回 -1。
source
串长度设为$n$,target
串长度设为$m$;
1 $O(n * m)$时间复杂度:暴力匹配
lintcode strstr,该题目不要求时间复杂度优化到$O(ncdot m)$;
class Solution {
public int strStr(String source, String target) {
if (source == null || target == null) {
return -1;
}
int n = source.length();
int m = target.length();
if (m == 0) {
return 0;
}
int i, j;
for (i = 0; i <= n - m; i++) {
for (j = 0; j < m; j++) {
if (source.charAt(i + j) != target.charAt(j)) {
break;
}
}
if (j == m) {
return i;
}
}
return -1;
}
}
2 $O(n+m)$复杂度:Rarbin-Karp算法
lintcode strStrⅡ,该题目要求时间复杂度优化到$O(n+m)$;
2.1 Rarbin-Karp算法思想
暴力匹配的时间开销$O(m)$在于需要检查以source
串每个字符开头的子串是否匹配,Rabin-Karp算法的思想是用尽量一一对应的散列值去代表每个字符串,有推论:
推论一:
hashcode
不相等的字符串字面值必然不同;推论二:
hashcode
相同的字符串,因为哈希冲突无法避免,可能字面值不同,需要继续进行一一字符检查;
优化效果:通过计算字符串的hashcode
,将字符串字符挨个比较的时间开销O(m)优化到比较hashcode
的时间开销$O(1)$;不过由于计算需要计算散列值,在匹配前需要进行预处理;
2.2 实现注意点说明
在实际运算中,极容易发生整数溢出bug,因此需要不断进行模运算,选取任意大数取模时应注意不易过大;
2.2 代码实现
散列函数参考java.lang.String
类,假定有字符串target
,字面值为$s_{m-1}s_{m-2}..s_{1}s_{0}$长度为$m$,target串的散列值计算公式如下:
$$
hash(target) = s_{m-1}31^{m-1} + s_{m-2}times31^{m-2} + ... + s_{1}times31^{1} + s_{0} 31^{0}
$$
实现步骤:
预处理:计算
target
串的散列值targetCode
和$31^{m-1}$扫描
source
串:维护扫描窗口大小为$m$,计算窗口字串的散列值hashcode
,并与targetCode
相比较
如果二者不相等,根据推论一,扫描窗口向后移动一个字符,新的hashcode
需要减去刚被移出扫描窗口的上一个字符$s$的权值$stimes31^{m - 1}$;
如果二者相等,继续比较字面值是否相同;
public class Solution {
public int strStr2(String source, String target) {
if (source == null || target == null) {
return -1;
}
int n = source.length();
int m = target.length();
if (m == 0) {
return 0;
}
int mod = 1000000;
int targetCode = 0;
int power = 1;
for (int i = 0; i < m; i++) {
targetCode = (targetCode * 31 + target.charAt(i)) % mod;
power = (power * 31) % mod;
}
int hashCode = 0;
for (int i = 0; i < n; i++) {
hashCode = (hashCode * 31 + source.charAt(i)) % mod;
if (i < m - 1) {
continue;
}
if (i >= m) {
hashCode = (hashCode - source.charAt(i - m) * power) % mod;
// 防止负数溢出
if (hashCode < 0) {
hashCode += mod;
}
}
if (hashCode == targetCode) {
if (source.substring(i - m + 1, i + 1).equals(target)) {
return i - m + 1;
}
}
}
return -1;
}
}
ps:segmentfault的makedown编辑器不支持LaTex吗,乘法符号打不出来,求指教:)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。