代码随想录day09|KMP

文章讲述了如何使用KMP算法解决字符串匹配问题,包括next数组的构建和在LeetCode中的应用,以及如何用移动匹配和KMP方法检测重复子字符串。

文章:代码随想录

状态:看完视频才能实现

leecode 28 实现str()

思路:

这道题就是实现字符串匹配算法。KMP的经典例题。我觉得KMP的终点就是理解next数组的形成,也是一个递推,kmp字符匹配的过程。

//next数组中找最大相同前后缀,其实也可以理解为Kmp的过程。从j位置起始遍历的看作字串,i位置开始的看作主串。
// 当i,j位置的字符相同时,他们同时往后遍历,每相同一个,他们的相同前后缀长度也会加一。
//但要知道相同前后缀长度的意义,可以理解为从前面拿几个数放到后面他们时相同的。
//所以当i,j位置的值不相同怎么办?我们要尝试看有没有长度更小的相同前后缀吧。所以我们看j-1位置的值,看不匹配位置之前我们所找到的最大前后缀的长度
//所以这意味着我们把字符串的前几个字符移到尾部都是相同的,那么在这个基础上再看下一个匹不匹配,如果匹配就直接j+1,不匹配还需要再往j-1的位置递推。
//不匹配的情况,可以直接让j指针指向j-1位置上的值。比如j-1的位置为1,那么意味着可以跳过一位字符来比较,所以当j指向下标1,那么其实就是需要被比较的字符的位置,0被跳过了。

其实next数组实现之后,kmp的流程实现起来也很类似,所以要理解这个过程。

代码:

 //那么匹配子串的流程就是同时声明两个指针,一个指向主串,一个指向子串。核心就是让主指针不回退,子指针回退。
    // 匹配则同时遍历,不匹配则找到next数组中,不匹配位置的上一个位置的值,让子指针回退到这个值的位置再重新开始遍历比较.
    //这也是个递推的过程,从头部与尾部相同的最长前缀位置再往后遍历,不匹配则继续往前递推。这样就可以利用之前已经获取到的信息来遍历。
    public static int  strStr(String haystack, String needle) {
        int res=-1;
        int[] next=getNext(needle);
        int j=0;
        for (int i = 0; i < haystack.length(); i++) {
            while(j>0 && haystack.charAt(i)!=needle.charAt(j)){j=next[--j];}
            if(haystack.charAt(i)==needle.charAt(j)){
                j++;
            }
            if(j==needle.length()){
                res=i-j+1;
                break;
            }


        }

        return res;

    }

    public static int[] getNext(String needle) {
        int[] next = new int[needle.length()];
        int j = 0;
        next[0] = 0;

        for (int i = 1; i < needle.length(); i++) {
            //next数组中找最大相同前后缀,其实也可以理解为Kmp的过程。从j位置起始遍历的看作字串,i位置开始的看作主串。
            // 当i,j位置的字符相同时,他们同时往后遍历,每相同一个,他们的相同前后缀长度也会加一。
            //但要知道相同前后缀长度的意义,可以理解为从前面拿几个数放到后面他们时相同的。
            //所以当i,j位置的值不相同怎么办?我们要尝试看有没有长度更小的相同前后缀吧。所以我们看j-1位置的值,看不匹配位置之前我们所找到的最大前后缀的长度
            //所以这意味着我们把字符串的前几个字符移到尾部都是相同的,那么在这个基础上再看下一个匹不匹配,如果匹配就直接j+1,不匹配还需要再往j-1的位置递推。
            //不匹配的情况,可以直接让j指针指向j-1位置上的值。比如j-1的位置为1,那么意味着可以跳过一位字符来比较,所以当j指向下标1,那么其实就是需要被比较的字符的位置,0被跳过了。
            while(j>0 && needle.charAt(j)!=needle.charAt(i) ){
                j=next[--j];
            }
            if(needle.charAt(j)==needle.charAt(i)){ j++;}

            next[i]=j;
        }
        return next;

leecode 48 重复的子字符串

思路:

有两种实现方法 移动匹配和KMP,kmp实现也有两种方法。

先说移动匹配:

//移动匹配 因为如果一个字符串s,是由一个子串s‘重复组成的话,那么这个主串的长度一定是s’的整数倍。
// 所以如果我们将主串拼接,并从1的索引位置开始寻找,如果最后最先找到的主串不是拼接进来的主串,则证明能被重复子串形成

代码:

    public boolean repeatedSubstringPattern(String s) {
        return (s + s).indexOf(s, 1) != s.length();

    }

KMP:

有两种实现方法:
//第一种是从s+s中对s进行模式串匹配,找到s第一次出现的位置,逻辑和上面一样,就是自己实现indexOf算法。
//第二种就是找到字符尾部时最长相同前后缀不包含的部分,如果len能整除那部分,那么那就是最小的重复子串。随想录上有证明。
//结论为当一个字符串由重复子串组成的,最长相等前后缀不包含的子串就是最小重复子串 即len-(next[len]);而如果是由重复子串组成的主串,最后位置的最长前后缀也是字符的最长相等前后缀。

代码:

public boolean repeatedSubstringPatternKMP(String s) {
        int j=0;
        int len=s.length();
        int [] next=new int[len];
        next[0]=0;
        for (int i = 1; i <len ; i++) {
            while(j>0 &&s.charAt(j)!=s.charAt(i)){
                j=next[--j];
            }
            if (s.charAt(j)==s.charAt(i)){j++;}
            next[i]=j;
        }
        //结尾处没有相同前后缀时一定不是相同子串重复组成, 需要对等于0时做特殊处理
        if(next[len-1]>0 && len%(len- next[len-1])==0){
            return true;
        }


        return false;

    }

### 关于KMP算法的不同资源讲解对比 #### 王道教材中的KMP算法介绍 王道教材对于KMP算法的描述集中在该算法的核心概念及其应用场景上。KMP算法由Knuth、Morris和Pratt三位学者发明,名称来源于三人的姓氏首字母组合[^1]。此算法主要用于字符串模式匹配领域,在遇到字符不匹配的情形下,能够通过已知的部分匹配信息调整搜索起点而无需重新开始整个过程。 为了实现这一点,KMP依赖于`next`数组(有时也称为部分匹配表),用于记录模式串中各个位置之前的子串的最大相同前后缀长度。这种机制使得一旦发生失配情况,可以根据之前计算好的最大公共前后缀的信息快速定位新的起始点继续查找工作,从而提高了效率。 #### 代码随想录里的KMP算法解析 相比之下,《代码随想录》一书则更加注重实践操作以及具体编码技巧方面的指导。书中不仅解释了什么是`next`数组——即用来存储针对每一个字符的位置所能找到最长相等真前缀与真后缀交集大小的数据结构;还特别强调理解和记忆之间的区别,指出仅仅依靠机械性的背诵难以长久保持知识点的记忆效果。 此外,《代码随想录》提供了详细的构建`next`数组的方法论,并且给出了完整的C++语言版本的具体实现案例,帮助读者更好地掌握这一经典算法的实际运用方式[^4]。 综上所述,两份资料各有侧重:前者偏向理论基础建设,后者更倾向于实战演练和技术细节分享。两者结合起来学习可以帮助使用者既获得扎实的知识体系又具备实际解决问题的能力。 ```cpp // 来自《代码随想录》,展示了如何创建 next 数组并应用于 KMP 字符串匹配算法 void getNext (int* next, const string& s){ next[0] = 0; int j = 0; for(int i = 1; i < s.size(); i++){ while(j > 0 && s[i] != s[j]) { j = next[j - 1]; } if(s[i] == s[j]) { j++; } next[i] = j; } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值