【字符串匹配】【KMP算法】Leetcode 28 找出字符串中第一个匹配项的下标☆

【字符串匹配】【KMP算法】Leetcode 28 找出字符串中第一个匹配项的下标

    • (1)前缀和后缀
    • (2)前缀表(最长相同的前缀和后缀的长度)
    • (3)匹配过程示意
    • (4)next数组的实现方法
      • 1.初始化
      • 2.处理前后缀不相等的情况 :
      • 3.处理前后缀相同的情况:
      • 4.求next数组的程序:
  • 题目做法
    • 解法1 KMP算法
    • 解法2 暴力做法

---------------题目链接-------------------

【字符串匹配】【KMP算法】Leetcode 28 找出字符串中第一个匹配项的下标☆_第1张图片


任务:要在文本串:aabaabaafa 中查找是否出现过一个模式串:aabaaf

(1)前缀和后缀

前缀是指不包含最后一个字符的,所有以第一个字符开头的连续子串。
比如aabaaf的前缀包括:a,aa,aab,aaba,aabaa
后缀是指不包含第一个字符的,所有以最后一个字符结尾的连续子串。
比如aabaaf的后缀包括:f,af,aaf,baaf,abaaf

(2)前缀表(最长相同的前缀和后缀的长度)

前缀表(最长相同的前缀和后缀的长度)
前缀表(最长相同的前缀和后缀的长度)
前缀表(最长相同的前缀和后缀的长度)
作用:记录下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀

模式串aabaaf的前缀表:

字符串 最长相等前后缀
a 0
aa 1
aab 0
aaba 1
aabaa 2
aabaaf 0

前缀表的任务:当前位置匹配失败,找到之前已经匹配上的位置,再重新匹配。
也意味着在某个字符失配时,前缀表会告诉你下一步匹配中,模式串应该跳到哪个位置。

文本串 a a b a a b a a f
模式串下标 0 1 2 3 4 5
模式串 a a b a a f
–前缀表– 0 1 0 1 2 0

当匹配到 b 的时候,模式串为 f ,匹配失败。
于是寻找 f 前面的字符串 aabaa, 他的最长相等前缀和后缀字符串是 aa , 因为找到了最长相等的前缀和后缀,匹配失败的位置是后缀子串的后面,那么我们找到与其相同的前缀的后面(即前缀表中发现冲突位置的前面的字符串——aabaa对应的前缀表为【2】,因此找到模式串中下标索引为【2】的位置 —— b 的位置开始)重新匹配就可以了。

文本串 a a b a a b a a f
模式串 a a b a a f

(3)匹配过程示意

(4)next数组的实现方法

next数组详解视频!
代码随想录文字版
!!!!!代码随想录视频版本!!!!!

1.初始化

【i:后缀的末尾】初始化为1,
【j:前缀的末尾】初始化为0 , next [ 0 ] = 0
j:也代表了i包括i之前的字符串的最长相等前后缀长度

2.处理前后缀不相等的情况 :

j连续回退 ———— j=next [ j-1 ], (在j大于0的情况下)

3.处理前后缀相同的情况:

j++  →  更新next数组:next [ i ] = j   →    i++

4.求next数组的程序:

1.初始化 【i:后缀的末尾】初始化为1,  【j:前缀的末尾,也代表i包括i前字符的最长相等前后缀长度】初始化为0 ,   next[0] = 0
2.处理前后缀不相等的情况
3.处理前后缀相同的情况

 //求前缀表next
    private void getNext(int[] next, String s){
        int j = 0;  // 初始化j为前缀末尾0,i为后缀的末尾
        next[0] = 0;
        for(int i = 1; i < s.length(); i++){ 
            while(j > 0 && s.charAt(j) != s.charAt(i)){ 
                j = next[j-1];
            }
            if(s.charAt(j) == s.charAt(i)){ // 如果相同,前缀末尾j++
                j++;
            }
            next[i] = j;  // 将前缀的长度给next[i]
        }
    } 

题目做法

解法1 KMP算法

时间复杂度O(N)
空间复杂度O(N)

  class Solution {
    public int strStr(String haystack, String needle) {
        if(haystack.length() < needle.length()) return -1;

        int[] next = new int[needle.length()];
        getNext(next, needle);
        int j = 0;
        for(int i = 0; i < haystack.length(); i++){
            while(j>0 && needle.charAt(j) != haystack.charAt(i)){ 
                j = next[j-1];
            }
            if(needle.charAt(j) == haystack.charAt(i)){
                j++;
            }
            if(j == needle.length()) {
                return i-needle.length()+1;
            }
        }
        return -1;
    }

    //求前缀表next
    private void getNext(int[] next, String s){
        int j = 0;  // 初始化j为前缀末尾0,i为后缀的末尾
        next[0] = 0;
        for(int i = 1; i < s.length(); i++){ 
            while(j > 0 && s.charAt(j) != s.charAt(i)){ 
                j = next[j-1];
            }
            if(s.charAt(j) == s.charAt(i)){ // 如果相同,前缀末尾j++
                j++;
            }
            next[i] = j;  // 将前缀的长度给next[i]
        }
    } 
}

解法2 暴力做法

从大字符串的第一个元素开始,比对小字符串,一旦出现不一样的就从大字符串的下一个元素开始进行比对
如果小字符串遍历结束时都一样,则return对应的下标
如果大字符串遍历完小字符串还没遍历完,return-1
遍历完大字符串前都找不到的话就return -1

时间复杂度O(N^2)
空间复杂度O(N)

class Solution {
    public int strStr(String haystack, String needle) {
        // 暴力做法
        char[] ch1 = haystack.toCharArray();
        char[] ch2 = needle.toCharArray();
        int result = -1;
        for(int i = 0; i < ch1.length; i++){ // haystack的遍历
            if(ch1[i] == ch2[0]){
                int outside = i;
                int inside = 0;
                while(ch1[outside] == ch2[inside]){
                    outside++;
                    inside++;
                    if(inside == ch2.length){return outside-ch2.length;}
                    else if(outside == ch1.length){return result;}
                }
            }
        }
        return result;
    }
}

你可能感兴趣的:(Leetcode,算法,leetcode,职场和发展,排序算法,java,数据结构)