LeetCode刷题之找出字符串中第一个匹配项的下标

2024/5/12 今天是母亲节,第一次跟妈妈说我爱您。虽然是用文字表达,老妈应该也很开心吧。朋友送了一本马尔克斯的《百年孤独》,很早就听说过这本书,但之前没有读过外国文学,也望而却步了。现接触了一些外国文学,有了一定的阅读基础,故是时候打开它了。但我现在在看塞万提斯的《堂吉诃德》,不想经典被我穿插鉴赏,有一种脚踏两只船的感觉。因此,《百年孤独》就得往后延延了。书归正传,且看题:

1、题目描述

在这里插入图片描述

2、逻辑分析

拿到这个题目,我想到的还是指针来处理。首先,遍历haystack字符串,找到与needle字符串第一个元素相等的位置,使用变量来存储索引。再依次遍历haystack,遍历的每一个元素都必须与needle里面的元素一一对应。最后return即可。

看了题解,发现跟我想的不太一样,暴力解法即让字符串 needle与字符串 haystack 的所有长度为 m 的子串均匹配一次,匹配出错就跳出此子串的匹配,开始下一子串匹配。匹配成功,返回子串开始位置,失败,返回-1。

还有一个大名鼎鼎的KMP算法,但是看题解有些看不懂,过于理论化,问问GPT。

KMP(Knuth-Morris-Pratt)算法是一种用于在一个文本字符串(通常称为“主字符串”或“主文本”)中搜索一个词(通常称为“模式”或“子串”)的线性时间复杂度的算法。这种算法由Donald Knuth、James H. Morris和Vaughan Pratt三人共同发明,用于改进朴素的字符串搜索算法。

朴素的字符串搜索算法(也称为暴力搜索算法)的时间复杂度是O(nm),其中n是主字符串的长度,m是模式的长度。但是,KMP算法的时间复杂度是O(n+m),这在实际应用中是非常高效的。

KMP算法的关键在于,当主字符串中的字符与模式的某个字符不匹配时,它能知道部分已经匹配的字符的信息,并利用这些信息来避免重新检查这些字符。

以下是KMP算法的基本步骤:

  1. 预处理模式:计算模式的“部分匹配表”(也称为“失败函数”或“跳转表”),该表用于在发生不匹配时确定下一个要比较的字符。
  2. 比较字符:从主字符串的第一个字符和模式的第一个字符开始,逐个比较它们。
  3. 匹配:如果所有的字符都匹配,那么返回模式在主字符串中的起始位置。
  4. 不匹配:如果某个字符不匹配,查看部分匹配表,确定模式中的下一个要比较的字符。
  5. 重复:重复步骤2-4,直到找到匹配或确定模式不在主字符串中。

详细的视频详解我就放这儿了:KMP原理演示。

3、代码演示

3.1、暴力解法

 public int strStr(String haystack, String needle) {
        int n = haystack.length(), m = needle.length();
        for(int i = 0 ; i + m <= n ; i++){
            // 假设当前位置i是needle的起始位置,设置标志位flag为true  
            boolean flag = true;
            // 在haystack中从位置i开始,检查长度为m的子串是否与needle相同 
            for(int j = 0; j < m ; j++){
                // 如果在任意位置,haystack的字符与needle的字符不相同
                if(haystack.charAt(i + j) != needle.charAt(j)){
                    // 将标志位flag设为false,并跳出内部循环 
                    flag = false;
                    break;
                }
            }
            if(flag){
                return i;
            }
        }
        return -1;
    }

暴力解法的时间复杂度为:O(n * m) , 空间复杂度为:O(1)。

3.2 KMP解法

public int strStr(String haystack, String needle) {
int n = haystack.length() , m = needle.length();
if( m == 0){
    return -1;
}
// 创建一个数组p,用于存储模式字符串needle的部分匹配表(或称为跳转表)  
int [] p = new int [m];
// 初始化pi数组,并构建部分匹配表  
// 变量i表示当前正在处理needle的哪个字符,j表示当前匹配到的最长公共前后缀的长度  
for(int i = 1 ,j = 0; i < m; i ++ ){
    // 当j大于0且当前字符不匹配时,通过p数组回溯,寻找更短的前后缀匹配 
    while(j > 0 && needle.charAt(i) != needle.charAt(j)){
        j = p[j -1];
    }
    // 如果当前字符匹配,或者j为0(即needle的第一个字符),则j自增 
    if(needle.charAt(i) == needle.charAt(j)){
        j++;
    }
    // 更新p[i],表示当needle的第i个字符不匹配时,应该跳转到needle的第pi[i]个字符继续比较 
    p[i] = j;
}
// 接下来在主字符串haystack中搜索模式字符串needle  
// 变量i表示当前正在检查haystack的哪个字符,j表示当前匹配到的needle的字符索引 
for(int i = 0, j = 0; i <n; i++){
    // 当j大于0且当前字符不匹配时,通过p数组回溯,寻找更短的前后缀匹配
    while(j > 0 && haystack.charAt(i) != needle.charAt(j)){
        j = p[j - 1];
    }
    // 如果当前字符匹配,j自增 
    if(haystack.charAt(i) == needle.charAt(j)){
        j++;
    }
    // 如果j等于m,说明needle的所有字符都匹配了,返回needle在主字符串haystack中的起始位置  
    // 注意这里要减去needle的长度m-1,因为j是从1开始的
    if(j == m){
        return i - m + 1;
    }
}
// 如果遍历完haystack后仍未找到与needle相同的子串,则返回-1 
 return -1;
}

时间复杂度:KMP时间复杂度为: O(m +n) ,空间复杂度:O(m)。

虽然写完了,理解了逻辑思想,但是代码部分还未完全掌握,接下来还需要进一步加强。

好啦,祝所有母亲,母亲节快乐!放一张邓丽君:

在这里插入图片描述

BYE!

  • 13
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值