题目: 字符串str1和str2 ,str1是否包含str2,如果包含str2在str开始的位置,如何做到时间复杂度O(N)完成?

题目: 字符串str1和str2 ,str1是否包含str2,如果包含str2在str开始的位置,如何做到时间复杂度O(N)完成?

解法: 用KMP 算法 求解,需要用到一个最长前缀值数组。
字符串中每个字符的最长前缀值是:当前字符前面的字符,前缀串 = 后缀串 ,最长的长度。

有了这个字符的前缀值数组,当在str1,和 str2匹配时候,str1[i] != str2[j],i 不需要回到 i-j+1 的位置了,i仍然不变,只是需要改变j,j = 最长前缀值即可。

这个最长前缀值,维护的是一个数组,即保存每个字符的最长前缀值:next[]。

生成next[]数组的java代码如下:

private static int[] getNextArray(char[] str2) {
        if (str2 == null || str2.length == 0){
            return null;
        }

        //1,创建next数组
        int[] next = new int[str2.length];

        //字符串前两个字符的最长前缀值,都可以直接确定
        next[0] = -1;
        next[1] = 0;

        //从第三字符开始计算
        int i = 2;

        //cn表示当计算到字符i的时候,需要用到i-1字符已经计算出来的最长前缀值 cn = next[i-1]
        // 如果字符i-1的最长前缀值,索引上面的那个字符 str[next[i-1]] 等于 i-1,
        // str[cn] == str[i-1] ?
        // 那么i字符的最长前缀值就等于next[i-1]+1
        // 我们把 cn 设置为 next[i-1],cn 从0开始
        int cn = 0;
        while (i < next.length){
            if (str2[cn] == str2[i-1]){
                //如果字符i的前一个字符i-1,的最长前缀匹配值next[i-1],
                // 这个位置上的字符str[next[i-1]] = str[i-1]
                //那么i字符的最长前缀值,等于i字符的最长前缀值+1, next[i] = cn+1;
                // 因为 i要继续向后移动,i要++,
                // i+1 字符,还有利用i字符计算出来的最长前缀值了,i利用的是i-1的cn,
                // 那么 i+1 需要利用i的 cn+1 了
                next[i] = cn+1;// next[i] = next[i-1]+1 也行
                i++;//向后移动
                cn++;//向后移动
            } else if (cn > 0){
                cn = next[cn];//如果 str2[cn] != str2[i-1], 那么cn需要向前移动了,移动多少呢?
                //需要借助字符 str[cn] 的最长前缀值了,就是 next[cn],
                // 有点类似于
                // 此时 cn 就是字符i-1, i
            } else {
                next[i++] = 0;//如果一直找不到,则当前字符i的最长前缀匹配值就是0了,然后i向后移动 i++
            }
        }
        return next;
    }

有了这个next[] 数组,kmp 算法就好解了。

应用KMP代码的JAVA代码如下:

public static int kmp(String s, String m){
        if (s == null || m == null || m.length() < 1 || s.length() < m.length()){
            return -1;
        }
        //1,字符串转换为字符数组
        char[] str1 = s.toCharArray();
        char[] str2 = m.toCharArray();
        int i1 = 0;
        int i2 = 0;
        int[] next = getNextArray(str2);//  前缀数组

        //2,开始匹配
        while (i1 < str1.length && i2 < str2.length){
            if (str1[i1] == str2[i2]){//如果字符匹配,则同时向后移动
                i1++;
                i2++;
            } else if (i2 == 0){//当str1[i1] != str2[i2],并且i2==0,说明完全匹配不上,则i1需要向后移动了
                i1++;
            } else {
                i2 = next[i2];//此时str1[i1] != str2[i2],为了节省匹配时间,不应该从str2的头部,即i2==0
                            //和此前str1的下一个位置比较,
                //因为计算了str2每个字符的最长前缀,此时i2位置的字符最长前缀就是 next[i2],从最长前缀位置开始再次比较
            }
        }
        return i2 == str2.length ? i1 - i2 : -1;
    }

调试:

public static void main(String[] args) {

        System.out.println(kmp("abcabstabcabe", "abcabe"));
    }

结果:
IDEA下调试结果

本文转载自 微信公众号,皮皮克克—可以叫我Tony 链接
https://mp.weixin.qq.com/s?__biz=MzkxOTM5NzUxMw==&mid=2247485547&idx=1&sn=7eea7cb81611bdfa19e8c49943717487&chksm=c1a3fd8df6d4749b9fc6b901cb0bd8f3159b0da233ccb2aeea8b626eefa671e064dc2a9d21ed&scene=126&sessionid=1685787950#rd

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值