LeetCode第28题思悟——strStr(implement-strstr)
知识点预告
- 注意算法的使用场景和优劣分析;
- 参数边界条件的处理
题目要求
给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。
链接:https://leetcode-cn.com/problems/implement-strstr
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
示例
示例 1:
输入: haystack = “hello”, needle = “ll”
输出: 2
示例 2:输入: haystack = “aaaaa”, needle = “bba”
输出: -1
说明:当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。
对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与C语言的 strstr() 以及 Java的 indexOf() 定义相符。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/implement-strstr
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
我的思路
作为一道简单题,这道题可以甚至可以直接通过Java String类提供的API完成,需要注意的是参数特殊情况的处理,比如空字符串和null;
但是在这里,说到底该题是字符串之间的匹配问题,所以我尝试使用了KMP算法,关于KMP算法可以参见我的这篇博文:KMP字符串匹配算法思悟;内容很详实;
但是,实际运行效果怎么样呢?不好,倒不是算法不好,而是算法应用的场景不匹配;具体的原因我会在差异分析一节中详细反思
public int strStr(String haystack, String needle) {
if(needle==null||needle.trim().equals("")){
return 0;
}
return match(needle,haystack);
}
private int[] getMoveInfo(char[] pattern) {
int m = pattern.length;
int[] info = new int[m];
int matchedNum = 0;
for (int i = 1; i < m; i++) {
while (matchedNum > 0 && pattern[matchedNum] != pattern[i]) {
matchedNum = info[matchedNum - 1];
}
if (pattern[matchedNum] == pattern[i]) {
matchedNum++;
}
info[i] = matchedNum;
}
return info;
}
public int match(String pattern, String source) {
int patternLength = pattern.length();
int sourceLength = source.length();
char[] p = pattern.toCharArray();
char[] s = source.toCharArray();
int[] moveInfo=getMoveInfo(p);
int matchedNum = 0;
int patternBorder = patternLength - 1;
for (int i = 0; i < sourceLength; i++) {
while (matchedNum > 0 && p[matchedNum] != s[i]) {
matchedNum = moveInfo[matchedNum - 1];
}
if (p[matchedNum] == s[i]) {
matchedNum++;
}
if (matchedNum == patternLength) {
return (i - patternBorder);
}
}
return -1;
}
优秀解法
//解法A
public int strStr(String haystack, String needle){
return haystack.indexOf(needle);
}
//解法B
public int strStr(String haystack, String needle) {
if(needle.equals(""))return 0;
for (int i=0;i<=haystack.length()-needle.length();i++){
if (haystack.substring(i,i+needle.length()).equals(needle)){
return i;
}
}
return -1;
}
//解法C
public int strStr(String haystack, String needle) {
if(needle.equals("")){
return 0;
}
char[] h = haystack.toCharArray();
char[] n = needle.toCharArray();
for (int i = 0; i < h.length-n.length+1; i++) {
if(h[i]==n[0]){
int j=1;
for (;j<needle.length();j++){
if(h[i+j]!=n[j]){
break;
}
}
if(j==needle.length()){
return i;
}
}
}
return -1;
}
差异分析
解法A为最简解法,充分利用语言特性,但是谈不上什么算法技巧;
解法B不断截取子串,然后和查询值作对比,倒也是一种思路,但是略显繁琐;
解法C则为常规解法,思路明确;
以上即为与题目要求相符合的解法;那么经典的KMP算法,为什么效率上反倒不如这几种算法呢?答案是:场景不合适;
KMP算法的核心分为两步:第一步是模式字符串的自匹配,在这道题中即为needle字符串;第二步则是模式字符串和待匹配字符串的匹配;我们这道题实际上只需要第2步;进行第一步的目的是为了优化第二步;
然鹅,这道题只是要求找出首次出现位置;而KMP发挥作用的场景应该是找出所有出现的位置;如此,第一步的额外代价才能在第二步中得到弥补,从而提高字符串匹配的效率;也就是说,我们付出了代价,却没有收获的机会;总而言之,这里有一点点大材小用,却没用好的味道;
另外,这道题的重点在于对needle的边界条件的考虑:needle为空字符串;好在题目也是给出了提示;但是面试官不会啊;所以要重视参数的边界条件;
解法A就没有问题了吗?如果haystack为null会怎样呢?我认为应该处理一下这种情况的,但还是既然能通过测试,一定程度上也说明无需考虑这种情况,但在实际开发中,还是“不要相信用户的输入”比较好;
知识点小结
- 注意算法的使用场景和优劣分析;
- 参数边界条件的处理