返回
暴击力学:用KMP武装你的暴力解法,轻松拿下LeetCode28!
后端
2023-11-02 04:08:55
引子:暴力解法的疾风骤雨
暴力解法以其简单粗暴的搜索方式,在字符串匹配的世界中闯出了一片天地。它就像一位蛮力十足的战士,用最直接的方式寻找目标。算法的核心思想是:
- 依次枚举haystack中的每个字符作为子字符串needle的起始位置。
- 对于每个起始位置,比较haystack和needle的子串,如果匹配,则返回起始位置;如果不匹配,则继续枚举下一个起始位置。
KMP算法:从暴力中升华的优雅
KMP算法,全称为Knuth-Morris-Pratt算法,是一种字符串匹配算法,以其高效和广泛的适用性而著称。它在暴力解法的基础上,加入了一个巧妙的优化策略——前缀表 。前缀表记录了needle中每个前缀与needle本身的最长公共前缀的长度。利用前缀表,我们可以快速跳过不匹配的部分,从而大幅提升搜索效率。
算法流程:化繁为简,势如破竹
KMP算法的流程可以概括为以下几个步骤:
- 预处理needle,构建前缀表。
- 将needle和haystack拼接成一个新的字符串。
- 使用一个指针i遍历拼接后的字符串。
- 将i处的字符与needle的相应字符比较。
- 若匹配,则i加1,继续比较下一个字符。
- 若不匹配,则根据前缀表,将i回退到needle中与当前字符匹配的最长公共前缀的末尾。
- 重复步骤4-6,直至遍历完拼接后的字符串。
代码实现:将算法之美化为代码之实
class Solution {
/**
* 使用KMP算法实现字符串匹配
*
* @param haystack 主串
* @param needle 子串
* @return 子串在主串中第一次出现的位置,如果没有出现则返回 -1
*/
public int strStr(String haystack, String needle) {
if (needle.isEmpty()) {
return 0;
}
// 构建前缀表
int[] prefixTable = buildPrefixTable(needle);
// 使用KMP算法匹配字符串
int i = 0;
int j = 0;
while (i < haystack.length()) {
if (haystack.charAt(i) == needle.charAt(j)) {
i++;
j++;
if (j == needle.length()) {
return i - j;
}
} else {
if (j > 0) {
j = prefixTable[j - 1];
} else {
i++;
}
}
}
return -1;
}
/**
* 构建前缀表
*
* @param needle 子串
* @return 前缀表
*/
private int[] buildPrefixTable(String needle) {
int[] prefixTable = new int[needle.length()];
prefixTable[0] = 0;
int i = 1;
int j = 0;
while (i < needle.length()) {
if (needle.charAt(i) == needle.charAt(j)) {
prefixTable[i] = j + 1;
i++;
j++;
} else {
if (j > 0) {
j = prefixTable[j - 1];
} else {
prefixTable[i] = 0;
i++;
}
}
}
return prefixTable;
}
}
结语:从暴力到优雅,算法之旅不止于此
在LeetCode28题中,我们从暴力解法入手,引入了KMP算法,用更优雅的方式解决了字符串匹配问题。无论是暴力解法还是KMP算法,都体现了算法设计中的巧妙与智慧。算法的世界无穷无尽,期待你在LeetCode的征途中不断探索,不断进步,不断创造属于自己的算法传奇!