给你两个字符串 haystack
和 needle
,请你在 haystack
字符串中找出 needle
字符串的第一个匹配项的下标(下标从 0
开始)。如果 needle
不是 haystack
的一部分,则返回 -1
。
示例 1:
输入:haystack = "sadbutsad", needle = "sad"
输出:0
解释:"sad
" 在下标 0
和 6
处匹配。
第一个匹配项的下标是 0
,所以返回 0
。
示例 2:
输入:haystack = "leetcode", needle = "leeto
"
输出:-1
解释:"leeto
" 没有在 "leetcode
" 中出现,所以返回 -1
。
提示:
1 <= haystack.length, needle.length <= 104
haystack
和 needle
仅由小写英文字符组成
c++class Solution {
public:
int strStr(string haystack, string needle) {
if(haystack.length()<needle.length()){
return -1;
}
for(int i=0;i<=haystack.length()-needle.length();i++){
for(int j=0;j<needle.length();){
if(needle[j]==haystack[i+j]){
j++;
}
else{
j=0;
break;
}
if(j==needle.length()){
return i;
}
}
}
return -1;
}
};
空间复杂度:
使用了常数级别的额外空间,没有使用额外的数据结构来存储信息。
时间复杂度:
其中,是的长度,是的长度。算法的时间复杂度主要取决于两个嵌套的循环。外层循环最多执行次,内层循环最多执行次。因此,总的时间复杂度为。
作者:力扣官方题解
来源:力扣(LeetCode)
思路及算法
我们可以让字符串 与字符串 的所有长度为 的子串均匹配一次。
为了减少不必要的匹配,我们每次匹配失败即立刻停止当前子串的匹配,对下一个子串继续匹配。如果当前子串匹配成功,我们返回当前子串的开始位置即可。如果所有子串都匹配失败,则返回 。
c++class Solution {
public:
int strStr(string haystack, string needle) {
int n = haystack.size(), m = needle.size();
for (int i = 0; i + m <= n; i++) {
bool flag = true;
for (int j = 0; j < m; j++) {
if (haystack[i + j] != needle[j]) {
flag = false;
break;
}
}
if (flag) {
return i;
}
}
return -1;
}
};
复杂度分析
时间复杂度:,其中 是字符串 的长度, 是字符串 的长度。最坏情况下我们需要将字符串 与字符串 的所有长度为 的子串均匹配一次。
空间复杂度:。我们只需要常数的空间保存若干变量。
思路及算法
算法,简称 算法,由 、 和 三人于 年联合发表。
算法的核心为前缀函数,记作 ,其定义如下:
对于长度为 的字符串 ,其前缀函数 表示 的子串 的最长的相等的真前缀与真后缀的长度。特别地,如果不存在符合条件的前后缀,那么 。其中真前缀与真后缀的定义为不等于自身的的前缀与后缀。
我们举个例子说明:字符串 的前缀函数值依次为 。
有了前缀函数,我们就可以快速地计算出模式串在主串中的每一次出现。
如何求解前缀函数
长度为 的字符串 的所有前缀函数的求解算法的总时间复杂度是严格 的,且该求解算法是增量算法,即我们可以一边读入字符串,一边求解当前读入位的前缀函数。
为了叙述方便,我们接下来将说明几个前缀函数的性质:
这样我们可以依据这两个性质提出求解 的方案:找到最大的 ,满足 : , 且 (这样就有 ,即 )。
注意这里提出了两个要求:
由 定义可知:
那么 符合第一个要求。如果 ,我们就可以确定 。 否则如果 ,那么 ,因为 ,所以 ,于是可以取 (1) 式两子串的长度为 的后缀,它们依然是相等的: 。
当 时,我们可以修改我们的方案为: 找到最大的 ,满足 1) , 且 (这样就有 , 即 。
注意这里提出了两个要求:
由 定义可知 符合第一个要求。如果 ,我们就可以确定 。
此时,我们可以发现 的取值总是被描述为 的结构(初始为 )。于是我们可以描述我们的算法: 设定 的初始值为 。我们只需要不断迭代 (令 变为 ) 直到 或 即可,如果最终匹配成功(找到了 使得 ),那么 ,否则 。
复杂度证明 时间复杂度部分,注意到 ,即每次当前位的前缀函数至多比前一位增加一,每当我们迭代一次,当前位的前缀函数的最大值都会减少。可以发现前缀函数的总减少次数不会超过总增加次数,而总增加次数不会超过 次,因此总减少次数也不会超过 次,即总迭代次数不会超过 次。
空间复杂度部分,我们只用到了长度为 的数组保存前缀函数,以及使用了常数的空间保存了若干变量。
如何解决本题
记字符串 haystack 的长度为 ,字符串 needle 的长度为 。 我们记字符串 haystack,即将字符串 needle 和 haystack 进行拼接,并用不存在于两串中的特殊字符 # 将两串隔开,然后我们对字符串 求前缀函数。
因为特殊字符 的存在,字符串 str 中 haystack 部分的前缀函数所对应的真前缀必定落在字符串 needle 部分,真后缀必定落在字符串 haystack 部分。当 haystack 部分的前缀函数值为 时,我们就找到了一次字符串 needle 在字符串 haystack 中的出现(因为此时真前缀恰为字符串 needle)。
实现时,我们可以进行一定的优化,包括:
我们也无需特别处理特殊字符 #,只需要注意处理字符串 haystack 的第一个位置对应的前缀函数时,直接设定 的初值为 0 即可。
第一部分是求 needle 部分的前缀函数,我们需要保留这部分的前缀函数值。
第二部分是求 haystack 部分的前缀函数,我们无需保留这部分的前缀函数值,只需要用一个变量记录上一个位置的前缀函数值即可。当某个位置的前缀函数值等于 时,说明我们就找到了一次字符串 needle 在字符串 haystack 中的出现(因为此时真前缀恰为字符串 needle,真后缀为以当前位置为结束位置的字符串 haystack 的子串),我们计算出起始位置,将其返回即可。
c++class Solution {
public:
int strStr(string haystack, string needle) {
int n = haystack.size(), m = needle.size();
if (m == 0) {
return 0;
}
vector<int> pi(m);
for (int i = 1, j = 0; i < m; i++) {
while (j > 0 && needle[i] != needle[j]) {
j = pi[j - 1];
}
if (needle[i] == needle[j]) {
j++;
}
pi[i] = j;
}
for (int i = 0, j = 0; i < n; i++) {
while (j > 0 && haystack[i] != needle[j]) {
j = pi[j - 1];
}
if (haystack[i] == needle[j]) {
j++;
}
if (j == m) {
return i - m + 1;
}
}
return -1;
}
};
复杂度分析
时间复杂度:,其中 是字符串 的长度,mmm 是字符串 的长度。我们至多需要遍历两字符串一次。
空间复杂度:,其中 是字符串 的长度。我们只需要保存字符串 的前缀函数。
本文作者:古月流新
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!