ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Leetcode 刷题笔记(八) —— 字符串篇之 KMP

2022-02-05 15:01:09  阅读:181  来源: 互联网

标签:子串 charAt int needle len next KMP Leetcode 刷题


KMP

刷题路线以及 KMP 详解 :代码随想录

什么是 KMP 算法?

KMP 是以 3 位发明者名字的首字母命名的,常用来字符串匹配上。如:判断 s1 是不是 s2 的子串,KMP 算法将解决此问题的时间复杂度从 O(m*n) 降低到了 O(n)
它的思想是当出现字符串不匹配时,可以记录一部分之前已经匹配的文本内容,利用这些信息避免从头再去做匹配。

如:文本串:aabaabaaf,模式串:aabaaf,判断 aabaaf 是否为 aabaabaaf 的子串
aabaabaaf
aabaaf
当在文本串下标 i = 5 处发现 b 和 f 不匹配时,模式串没有从文本串下标 i = 1 和 i = 2 开始从头比较,
aabaabaaf
. aabaaf
. . aabaaf
而是让下标 i 不回退,改变模式串下次比较的下标,在 aabaaf 的 f处 匹配失败,但是我们发现 f 前边的 aa (. . . aaf 和 开头的 aa. . . .) 相同 而因为已经匹配到 f 说明 f 前的 aabaa(aabaaf) 一定与 文本串(aabaabaaf)的 aabaa 已经完全匹配,那么文本串(aabaabaaf)发生匹配失败的前的 aa 一定在 模式串(aabaaf)的前缀出现。此时就可以直接去比较两个 aa 后边的字符就可以了。
aabaabaaf
. . . aabaaf

next 数组

数组next[i] 保存模式串[0, i] 区间子串的最长相同前后缀的长度,在匹配失败时,用来找到下次应该匹配的位置。最大相等前后缀的长度作为下标刚好是前缀尾部后一个位置,刚好作为匹配失败模式串指针回退的位置。

题录

28. 实现 strStr()

Leetcode 链接
实现 strStr() 函数。

给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回 -1 。
说明:
当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。
对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与 C 语言的 strstr() 以及 Java 的 indexOf() 定义相符。

示例 1:
输入:haystack = “hello”, needle = “ll”
输出:2

示例 2:
输入:haystack = “aaaaa”, needle = “bba”
输出:-1

示例 3:
输入:haystack = “”, needle = “”
输出:0

题解: 我们称 haystack = “hello”, needle = “ll” 中前者为文本串,后者为模式串
方式一:暴力解,第一层循环遍历文本串表示相同子串开始位置,第二层循环遍历模式串

class Solution {
    public int strStr(String haystack, String needle) {
        int hLen = haystack.length();
        int nLen = needle.length();
        // 模式串为空,返回0
        if (nLen == 0) return 0;
        // i 表示文本串该位置作为匹配的起始位置,所以 循环到hLen - nLen 结束,因为再往后一个位置作为匹配的起始位置,文本串剩余位置不够模式串匹配
        for (int i = 0; i <= hLen - nLen; i++) {
            if (haystack.charAt(i) == needle.charAt(0)) {
            	// 文本串 i 下标位置和模式串0下标位置已经匹配上,从 i+1 位置开始向后匹配
                int j = 1; 
                // j 指向模式串每轮匹配位置,则文本串每轮匹配位置为 i + j
                while (j < nLen && haystack.charAt(i + j) == needle.charAt(j)) {
                	// 当前字符匹配,j 指向下轮要匹配的位置
                    j++;
                }
                // 如果已经匹配到最后一个位置,表示整个模式串匹配成功,返回本轮匹配的起始位置
                if (j == nLen) return i;
            }
        }
        // 没有匹配成功
        return -1;
    }
}

KMP解法:

class Solution {
    public int strStr(String haystack, String needle) {
        int hLen = haystack.length();
        int nLen = needle.length();
        if (nLen == 0) return 0;
        // j 为模式串指针,表示接下来模式串比较的位置
        int j = 0;
        // 得到模式串对应的 next 数组
        int[] next = getNext(needle);
        // i 为文本串指针,表示接下来文本串比较的位置
        // 这里循环与暴力解法结束位置不同需要注意,因为这里的 i 表示接下来文本串比较的位置,i 要走到头
        for (int i = 0; i < hLen; i++) {
            // 如果不相等,j 回退,而 i不动
            while (j > 0 && haystack.charAt(i) != needle.charAt(j)) {
                // j 回退到[0,j)子串的最大相等前后缀的前缀尾后一个位置,如果没有相等的前后缀直接回到0 下标
                j = next[j - 1];
            }
            // 如果相等 j++,循环结束 i也++,表示接下来比较的位置
            if (haystack.charAt(i) == needle.charAt(j)) {
                j++;
                // 模式串指针 j 已经走到最后,成功匹配
                if (j == nLen) return i - nLen + 1;
            } 
        }
        return -1;
    }
    public int[] getNext(String s) {
        int len = s.length();
        // 1. 初始化
        int[] next = new int[len];
        int j = 0;   // 前缀指针 j=0, 后缀指针 i=1
        // j 代表接下来应该比较的前缀,同时代表这最大相等前后缀长度;i 代表接下来应该比较的后缀
        for (int i = 1; i < len; i ++) {
            // i 接下来需要比较的后缀,同时也表示 next 数组本轮填充位置
            // 不相等时回退
            while (j > 0 && s.charAt(i) != s.charAt(j)) {
                // j 回退到[0,j)子串的最大相等前后缀的前缀尾后一个位置,如果没有相等的前后缀直接回到0 下标
                j = next[j - 1];
            }
            // 相等时 j++,表示最大相等前后缀长度+1,同时指向下一次应该比较的前缀位置
            if (s.charAt(i) == s.charAt(j)) {
                j++;
            }
            // next数组 i 下标填充 [0, i] 子串的最大相等前后缀长度
            next[i] = j;
        }
        return next;
    }
}

459. 重复的子字符串

Leetcode 链接
给定一个非空的字符串 s ,检查是否可以通过由它的一个子串重复多次构成。
示例 1:
输入: s = “abab”
输出: true
解释: 可由子串 “ab” 重复两次构成。

示例 2:
输入: s = “aba”
输出: false

示例 3:
输入: s = “abcabcabcabc”
输出: true
解释: 可由子串 “abc” 重复四次构成。 (或子串 “abcabc” 重复两次构成。)
题解:
暴力解法:以前 i 个字符作为子串是否能被重构,第一层循环遍历所有 i 的可能,第二次循环使用双指针判断能否被重构

    public boolean repeatedSubstringPattern(String s) {
        int len = s.length();
        // i 表示子串长度,从 1 到最大 len/2
        for (int i = 1; i <= len / 2; i++) {
        	// 如果子串长度为 len 的因数,表示可能被 [0,i) 区间子串重构
            if (len % i == 0) {
                int j = 0;
                // 双指针 j 和 j + i 位置逐个比较
                for (; j < len - i; j ++) {
                    if(s.charAt(j) != s.charAt(j + i)) {
                    	// 一旦必相等,表示不能被 [0,i) 区间子串重构
                    	break;
                    }
                }
                // 如果是因为越界结束循环,表示可以被重构
                if (j + i == len) return true;
            }
        }
        return false;
    }

巧解:
举个栗子: 如果能被子串重构,那么原字符串经过循环移位后一定能等于原字符串
abab
. baba
. . abab
abababab 也就是说满足原字符串是两个原字符串拼接再一起去头去尾 bababa 的子串

    public boolean repeatedSubstringPattern(String s) {
	    String str = s + s;
	    return str.substring(1, str.length()-1).contains(s);
    }

KMP解法:
举个栗子:
s = “abcabcabcabc” 对应 next 数组为:000 123 456 789,s.length() = 12 = 9(next 数组最后一位) + 3(重构子串长度),3 是 s长度的因数,所以 s 能被子串重构

class Solution {
    public boolean repeatedSubstringPattern(String s) {
        int len = s.length();
        int[] next = new int[len];
        // 前缀指针 j
        int j = 0;
        // 填充 next 数组
        for (int i = 1; i < len; i++) {
            while (j > 0 && s.charAt(j) != s.charAt(i)) {
                j = next[j - 1];
            }
            if (s.charAt(j) == s.charAt(i)) {
                j++;
            }
            next[i] = j;
        }
        // 子串长度不能等于 s 长度
        if (next[len - 1] == 0) return false;
        // 子串长度:len - next[len - 1] 
        return (len % (len - next[len - 1]) == 0);
    }
}

标签:子串,charAt,int,needle,len,next,KMP,Leetcode,刷题
来源: https://blog.csdn.net/a1241692733/article/details/122761425

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有