继续打卡算法题,今天学习的是LeetCode的第28题找出字符串中第一个匹配项的下标,这道题目是道简单题
。算法题的一些解题思路和技巧真的非常巧妙,每天看一看算法题和解题思路,我相信对我们的编码思维和编码能力有一些提升。
分析一波题目
这个题目就是需要实现Java中,String的indexOf
方法的功能,我们使用暴力解法是可以求解出来的。原字符串s,目标字符串t,循环原字符,以原字符串中每个字符i作为开始比较的字符,如果匹配不到target字符串,开始字符从i+1开始。
比如原始字符串s : aabaacaadaae
, 目标字符串t : aacaad
他们的匹配过程如下:依次取原始字符串的一个字符作为起始字符开始匹配目标字符串,直到匹配为止
到第4轮的时候匹配上了,这种解法时间复杂度是O(m*n)。有没有更高效率的呢?
答案是有的,那就是kmp算法。kmp算法是利用一张前缀表来解决这个匹配问题的。
前缀表是什么呢?
比如目标字符串:aacaad
,他对应前缀表如下:
字符串a
相同前后缀是0
字符串aa
相同前后缀是1
字符串aab
相同前后缀0
字符串aaba
相同前后缀1
字符串aabaa
相同前后缀2
字符串aabaad
相同前后缀0
从下图可以看出,匹配过的aa可以不用再次匹配了,实现了剪枝效果。
编码实现
- 暴力解法
class Solution {
public int strStr(String haystack, String needle) {
int n = haystack.length(), m = needle.length();
for (int i = 0; i + m <= n; i++) {
if(haystack.charAt(i) != needle.charAt(0)) {
continue;
}
boolean flag = true;
for (int j = 0; j < m; j++) {
if (haystack.charAt(i + j) != needle.charAt(j)) {
flag = false;
break;
}
}
if (flag) {
return i;
}
}
return -1;
}
}
- kmp算法
class Solution {
//获取前缀表
private void getNext(int[] next, String s) {
int j = 0;
next[0] = 0;
//下面逻辑,找以i下标结尾的字符的最长公共前后缀,并且从第二个字符开始查找
for (int i = 1; i < s.length(); i++) {
//前后缀不相等 不断回溯
while (j > 0 && s.charAt(j) != s.charAt(i))
j = next[j - 1];
//连续相同,相等前缀自增 不断往前累加
if (s.charAt(j) == s.charAt(i))
j++;
next[i] = j;
}
}
public int strStr(String haystack, String needle) {
if (needle.length() == 0) return 0;
int[] next = new int[needle.length()];
//构造前缀表
getNext(next, needle);
int j = 0;
//往前遍历原始数组
for (int i = 0; i < haystack.length(); i++) {
//遇到了不相同的,查找前缀表
while (j > 0 && needle.charAt(j) != haystack.charAt(i))
j = next[j - 1];
//连续相等,相等前缀+1
if (needle.charAt(j) == haystack.charAt(i))
j++;
if (j == needle.length())
return i - needle.length() + 1;
}
return -1;
}
}
总结
kmp算法是解决字符串匹配效率较高的算法思想,可以减少原始字符串的回溯匹配,但是很难理解,需要多实践几遍,算法技巧真的是一个非常有意思的,值得收藏研究的技能。