算法力扣刷题 二十五【28.找出字符串中第一个匹配项的下标】

前言

字符串篇,继续。
记录 二十五【28.找出字符串中第一个匹配项的下标】


一、题目阅读

给你两个字符串 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 仅由小写英文字符组成

二、尝试实现

思路一

第一反应遍历haystack,如果首字母匹配,固定i;开启第二层循环,看是否全字匹配。应该可以。尝试。
(1)第一次尝试,遇到的问题:

class Solution {
public:
    int strStr(string haystack, string needle) {
            for(int i = 0;i < haystack.size();i++){
                if(haystack[i] == needle[0]){
                    int k = 1;//指针指向needle

                    for(int j = i+1;j < haystack.size();j++){
                        if(k < needle.size() && haystack[j] != needle[k++]){
                            i = j;
                            break;    
                        }else if(k == needle.size()){
                            return i;
                        }
                    }
                }
            }
            return -1;
    }
};
  • int k = 1;找到第一个字母匹配,想从第二个字母开始判断。所以j = i +1。return i在第二层for循环里面,这是默认haystack有2个及以上的字母。如果haystack是单个字母,无法进入j的遍历。如下case不成立:

      haystack ="a";needle =	"a"。这个case无法通过。
    

    所以,第一次修正。

(2)第二次尝试,修正第一个问题:

class Solution {
public:
    int strStr(string haystack, string needle) {
            for(int i = 0;i < haystack.size();i++){
                if(haystack[i] == needle[0]){
                    int k = 0;//指针指向needle

                    for(int j = i;j < haystack.size();j++){
                        if(k < needle.size() && haystack[j] != needle[k++]){	//等式不成立,不影响k++操作
                            i = j;
                            break;    
                        }else if(k == needle.size()){	//因为判断是否相等时操作k++,最后k会超出needle的下标,所以上面k < needle.size()也要判断
                            return i;
                        }
                    }
                }
            }
            return -1;
    }
};
  • 这一遍出现的问题:i =j;以为从i起始,往后长度是needle和needle单词不是全字匹配,就空掉j个长度。可能是交叉出现的,例如下面case:

      haystack ="mississippi";
      needle ="issip"。
      错误原因:但i = 1,j = 5因为不相等,需要break;但把i = j ,跳过正确答案return 4.
    

    所以i要一步一步往后走,不能跳过。

(3)再次修正

class Solution {
public:
    int strStr(string haystack, string needle) {
            for(int i = 0;i < haystack.size();i++){
                if(haystack[i] == needle[0]){
                    int k = 0;//指针指向needle
                    for(int j = i;j < haystack.size();j++){
                        if(k < needle.size() && haystack[j] != needle[k++]){
                           
                            break;    
                        }else if(k == needle.size()){
                            return i;
                        }
                        // k++;如果k++放到这里,if里面的k++要取消,同时,k == needle.size()-1。
                    }
                }
            }
            return -1;
    }
};

本次测试通过,完成实现


三、代码随想录学习

学习内容

KMP算法——解决字符串匹配的问题。在“文本串”中找“模式串”。对应题目:haystack是“文本串”;needle是“模式串”。

(1)为什么KMP算法找字符串匹配能更快?
答:

  • 二、中的代码就是暴力实现。i在文本串中一步一步后移,第一个if判断haystack[i]和needle[0]比较。所以如果不是全字匹配,i后移一位,重新和needle[0]比较。总结:每轮遇到不相等的字母,新开一轮,和模式串的初始位置比较

  • 新开一轮后,如果能从模式串中间的某个位置继续比较,看起来比重头来过要好。那咋知道遇到不相等字母时,从中间的哪个位置开始呢?跳到哪个下标可以继续呢?答案:

  • 前缀表。解释:它是一个数组,起个名字叫“next”。长度和模式串的长度一样。如果当前位置字母不相等,查看前一个位置的前缀表数值,跳转到该下标。假设从B位置跳到A位置,说明A位置之前的区间(不包含A)等同于从B位置往前数长度A这个区间(看图)。
    在这里插入图片描述
    (2)发现:从下标6跳转到下标2,下标2前面的区间(不包含2):c a。和从下标6往前数2个的区间:c a一样。

      对于“cacbca”:前缀和后缀相等的最长长度是2:
      前缀=c a。后缀=c a(不能是a c,所以不是对称的意思)
    

(3)原理:

位置B跳转到位置A——

  • A之前的区间(不包含A) ,这段长度为x。
  • B往前数长度x,不包含B的区间。(下图展示了两段区间)
  • 以上两个范围内容一模一样,也就是包含j-1的区间中前缀和后缀一样。找前缀=后缀长度的最大值,为了回退最少。
    在这里插入图片描述

代码实现

前缀表的数值不做任何改动,也不去右移/减1再加1。遇到不相等的位置,就看前一个元素前缀表数值多少,跳转到对应下标。

class Solution {
public:
        void getNext(int* next,string& s){
        next[0] = 0;//第一位没有前缀
        int j = 0;//前缀=后缀最长时,j指向前缀的末尾。也就是往next里面放的值。
        for(int i = 1;i < s.size();i++){
            while(j > 0 && s[j] != s[i]){
                j = next[j-1];
            }
            if(s[j] == s[i]){   //前缀=后缀最长时,可能中间有重叠。
                j++;
                
            }
            next[i] = j;
        }
    }
    int strStr(string haystack, string needle) {
        int size = needle.size();
        int* next = new int[size]{0};
        getNext(next,needle);//用这个函数来获取前缀表,传进去一个数组用来装值,和处理的字符串

        for(int i = 0;i < size;i++){
            cout<<next[i]<<'\t';
        }

        int j = 0;
        
        for(int i = 0;i < haystack.size();i++){
            
            while(j > 0 && needle[j] != haystack[i]){
                j = next[j-1];
            }
            if(haystack[i] == needle[j]){
                j++;
            }
            if(j == needle.size()){
                delete[] next;
                return (i-needle.size()+1);
            }
        }
        delete[] next;
        return -1;
    }
};

(1)问题:为什么求前缀表当i和j不相等时,j = next[j-1],而不是j一点一点的往前退,可以一下跳转到next[j-1]?
答:细想绿色两段相同,并且是最大长度下的相同
@danaaaaa


总结

  • KMP算法解决匹配问题。当遇到不匹配项回退重新比较可以不用从头开始,可以在中间的某个位置继续进行。
  • 如何确定在哪个位置继续比较?用前缀表。前缀表保证:从下标B跳转到下标A,0~ (A-1)下标范围和(B-A)~(B-1)下标范围内容一样,可以不再重复比较。
  • 求前缀表的时候:初始化——当s[j] != s[i]时,跳转到下标=next[j-1]处;——当s[j] == s[i] 时,j++,前缀增加一位。

(欢迎指正,转载标明出处)

  • 51
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值