原题
实现 strStr() 函数。
给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。
示例 1:
输入: haystack = "hello", needle = "ll"
输出: 2
示例 2:
输入: haystack = "aaaaa", needle = "bba"
输出: -1
说明:
当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。
对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与C语言的 strstr() 以及 Java的 indexOf() 定义相符。
来源:力扣(LeetCode)
LeetCode No.28 Implement strStr()
前言
有了前两道题的经历,我第一想法就是拿两个指针走的暴力解法,答案的更优解法(Rabin Karp),我看了好一会儿才知道是怎么实现的,第二种解法确实优化了好些地方。
我的解法
话不多说,先献上代码:
class Solution {
public int strStr(String haystack, String needle) {
if (needle.length() == 0) return 0;
char[] a = haystack.toCharArray();
char[] b = needle.toCharArray();
int j = 0;
for (int i = 0; i < haystack.length(); i++) {
if (a[i] == b[0]) {
while (j < needle.length() && j + i < haystack.length()) {
if (a[i + j] == b[j]){
j++;
} else {
j = 0;
break;
}
}
if (j == needle.length()) return i;
}
}
return -1;
}
}
在haystack中从头开始找有没有和needle的头字符相同的字符,如果有,则开始迭代判断,判断下一个是否为相同字符,直到遇到不相同的字符时退出这一轮迭代,或者这一轮迭代的字符与needle字符串全部相同,则返回答案(开始迭代的haystack字符下标)。
执行结果如下:
真叫一个惨目忍睹啊。。。
写完之后翻阅了一下答案的写法
答案解法1
public class Solution {
public int strStr(String haystack, String needle) {
int L = needle.length();
int n = haystack.length();
for (int start = 0; start < n - L + 1; start++) {
if (haystack.substring(start, start + L).equals(needle)) {
return start;
}
}
return -1;
}
}
作者:LeetCode
来源:力扣(LeetCode)
思路大同小异,但是我发现,原来我java基础不牢…都不知道有这个函数可以使用的。
substring(start, start + L):字符串haystack的子串(下标为start到start + L),然后equals()方法比较字符串是否相等。
执行结果还让我挺意外的:
答案解法2
这个答案的方法,我就特别难想到了,因为大二上学期才转到计算机专业的,数据结构自学学了个寂寞…
此方法就避免了每一次判断,都要从子串的最开头部分开始遍历判断每一个字符,显而易见,这样效率会被拉低,因此,有了Rabin Karp(常数复杂度)解法。
代码如下:
class Solution {
// function to convert character to integer
public int charToInt(int idx, String s) {
return (int)s.charAt(idx) - (int)'a';
}
public int strStr(String haystack, String needle) {
int L = needle.length(), n = haystack.length();
if (L > n) return -1;
// base value for the rolling hash function
int a = 26;
// modulus value for the rolling hash function to avoid overflow
long modulus = (long)Math.pow(2, 31);
// compute the hash of strings haystack[:L], needle[:L]
long h = 0, ref_h = 0;
for (int i = 0; i < L; ++i) {
h = (h * a + charToInt(i, haystack)) % modulus;
ref_h = (ref_h * a + charToInt(i, needle)) % modulus;
}
if (h == ref_h) return 0;
// const value to be used often : a**L % modulus
long aL = 1;
for (int i = 1; i <= L; ++i) aL = (aL * a) % modulus;
for (int start = 1; start < n - L + 1; ++start) {
// compute rolling hash in O(1) time
h = (h * a - charToInt(start - 1, haystack) * aL
+ charToInt(start + L - 1, haystack)) % modulus;
if (h == ref_h) return start;
}
return -1;
}
}
作者:LeetCode
来源:力扣(LeetCode)
有一说一,要我直接看代码我是看不懂的,不过官方的解析还是挺明确的
我对其理解就是,把子串全部生成为哈希码,打个比方,如要在字符串abcde中找到bcde,操作为:首先,abcde的子串abcd用整型数字1234表示,若要滑动字符串变成bcde,则操作为:(1234 - 1000)* 10 + 5, 则变成了2345,最后直接判断这个哈希码与目标字符串的哈希码是否相同就可以了。
总结
第一种方法很容易想到而且可以很快就写出来,第二种(哈希)写法特别高级,很值得去学习,但是我们要注意到它的很多细节部分,比如我们该如何设计一个公式来存储目标,如何避免溢出(比如哈希值太大,大于整型的最大值)(一般情况下用取模解决),避免了溢出问题,又该考虑怎样能避免散列冲突,之后我专门总结一下散列的相关知识点,来搞清这些问题。