【数据结构】字符串 | KMP算法

60 阅读2分钟

题目28. 找出字符串中第一个匹配项的下标 - 力扣(LeetCode)

image.png

思路

haystack = "aabaabaaf"
needle = "aabaaf"
暴力解法,每次怎样找到之前匹配过的内容?

image.png

f不匹配,此时,寻找f前面aabbaa的最长相等前后缀,也就是2,这个2告诉我们,f前面的字符串存在着前缀aa和后缀aa相等,并且这是最长的相等前后缀;理解这一点之后,再来看此时指针应该怎么移动:

  • 若不使用kmp算法,f不匹配,指针应该移动到0;指向文本串的指针后移一位
  • 使用kmp算法,f不匹配,但f前面的aabaa已经匹配上了,这里隐含着,f前面的aa(相等前后缀)已经匹配上了,我们不能浪费这个信息,此时指针不用回退到开头,可以直接从b开始,看看b是否和文本串匹配,继续向后遍历

总结

KMP算法最大化地使用已经匹配过的字符串信息,不浪费任何一个相等前后缀

  • 当字符不匹配时,文本串不回退,通过prefix确定模式串回退位置,即模式串的下一个查找值,时间复杂度是o(m)
  • prefix的计算,利用双指针,时间复杂度同样是o(n)
  • KMP算法复杂度是o(m) + o(n),暴力解法是o(m*n)

代码

  1. 计算前缀表-prefix数组
    // 求前缀表
    public int[] getPerfix(String s) {
        int[] prefix = new int[s.length()];
        prefix[0] = 0;
        if(s.length() < 2) {
            return prefix;
        }
        // int fast = 1;//后缀末尾
        int slow = 0;//前缀末尾
        //abcdabcx
        for(int fast = 1;fast < s.length();fast++) {
            // 不相等的情况要一直回退,所以用while
            while(slow > 0 && s.charAt(fast) != s.charAt(slow)){
                // 不相等的话slow要回退到fast前一个指定的位置
                slow = prefix[slow-1];
            }
            // 相等的情况
            if(s.charAt(fast) == s.charAt(slow)) {
                //相等前后缀长度为slow+1
                slow++;
            }
            prefix[fast] = slow;
        }
        return prefix;
    }
  1. 匹配字符串
        public int strStr(String haystack, String needle) {
        int flag = -1;
        if(haystack.length() < needle.length()) return flag;
        int[] prefix = getPerfix(needle);
        int fast = 0;//在haystack中遍历,不会退
        int slow = 0;//在slow中遍历,根据prefix回退
        for(;fast < haystack.length();fast++) {
            // 不相等的情况要一直回退,所以用while
            while(slow > 0 && haystack.charAt(fast) != needle.charAt(slow)){
                // 不相等的话slow要回退到fast前一个指定的位置
                slow = prefix[slow-1];
            }
            // 相等的情况
            if(haystack.charAt(fast) == needle.charAt(slow)) {
                //相等长度为slow+1
                slow++;
            }
            if(slow == needle.length()) {
                return fast-slow+1;
            }
        }
        return flag;
        
    }

image.png

问题:以上代码用到了字符串charAt()方法,所以速度比较慢,下面改为字符数组char[]的写法

class Solution {
    public int strStr(String haystack, String needle) {
        char[] h = haystack.toCharArray();
        char[] n = needle.toCharArray();
        int[] prefix = getPrefix(needle);
        int slow = 0;
        for(int fast = 0;fast < h.length;fast++) {
            //不相同情况
            while(slow > 0 && h[fast] != n[slow]) {
                slow = prefix[slow - 1];
            }
            if(h[fast] == n[slow]) {
                slow++;
            }
            if(slow == n.length) {
                return fast - slow + 1;
            }
        }
        return -1;
    }
    public int[] getPrefix(String s) {
        char[] ch = s.toCharArray();
        int slow = 0;
        int[] prefix = new int[ch.length];
        prefix[0] = 0;
        if(ch.length < 2) return prefix;
        for(int fast = 1;fast < ch.length; fast++) {
            // 不相等情况
            while(slow > 0 && ch[slow] != ch[fast]) {
                slow = prefix[slow-1];
            }
            // 相等情况
            if(ch[slow] == ch[fast]) {
                slow++;
            }
            prefix[fast] = slow;
        }
        return prefix;
    }
}