LintCode13 · 字符串查找(KMP算法实现)

题目描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

解法一:暴力求解

暴力求解就不多说什么废话了,直接上代码了

    public int strStr(String source, String target) {
        char[] s = source.toCharArray();
        char[] t = target.toCharArray();
        int m = s.length;
        int n = t.length;
        int i = 0, j = 0;
        if (n == 0)
            return 0;
        while (i < m && j < n) {
            if (s[i] == t[j]) {
                i++;
                j++;
            } else {
                i = i - j + 1;
                j = 0;
            }
            if (j == n)
                return i - j;
        }
        return -1;
    }

#解法二:KMP算法

关于这个算法,有一篇博文讲解的蛮好蛮详细的,直通KMP算法,但是感觉在求next数组的讲解这里还是有点,不太符合我的理解风格,这里我尽量用言简意赅的语言解释一波,希望可以帮助到不理解的朋友,也是记录一下自己现在的理解,防止以后忘记了还可以翻看一波。
先上求next数组的代码:

    int[] getNext(String target) {
        char[] p = target.toCharArray();
        int[] next = new int[p.length];
        next[0] = -1;
        int j = 0;
        int k = -1;
        while (j < p.length - 1)
            if (k == -1 || p[j] == p[k]) {
                next[++j] = ++k;
            } else
                k = next[k];
        return next;
    }

关于这个函数,上面的博文已经讲解的很详细了,理解的难点关键就在于else的k = next[k]这一句上,为什么,k要回退到next[k]呢?
首先,请牢记我们要求的next数组的值是什么,next[i]表示t字符数组前(i-1)个字符中的最大字串长度,务必牢记!!!

假设目标字符数组为t,当 t[j] == t[k] 时,t[j+1] 的最大子串的长度为 k,即 next[j+1] = k+1。但是此时t[j] != t[k] 了,所以就有 next[j+1] < k,那么求 next[j+1] 就等同于求 t[j] 往前小于 k 个的字符(包括t[j],看下图蓝色框框)与 t[k] 前面的字符(绿色框框)的最长重合串,即 t[j-k+1] ~ t[j] 与 t[0] ~ t[k-1] 的最长重合串;看到这里,有没有觉得很熟悉,找两个字符串的重合部分,那不就是拿着一个字符串去跟另外一个字符串进行逐个字符的比较嘛,那跟我们这个题目的目的是一样的呀,所以这里就有一种套娃的感觉,我们为了在一个主串中返回模式串的首次出现位置而需要求KMP算法的next数组,而next[i]表示t字符数组前(i-1)个字符中的最大子串长度,而我们求这个最大子串又感觉是从t[j-k+1] ~ t[j] 中寻找t[j-k+1] ~ t[j]和t[0] ~ t[k-1]的 公共子串。说白了,我们之所以想要用KMP算法,就是在source[i]和target[j]字符不相等的时候,不希望像暴力求解那样让i移动到i+1,j重新归零,我们想要让j回溯到next[j],而现在,下图中的j就是原问题的i,而k变成了原问题的j,原来的j回溯到next[j],那么现在问题中的k自然也是要回溯到next[k]才是最节省判断步数的,所以才会有k=next[k]。
在这里插入图片描述
至于剩下的KMP算法,以及后面对于getNext函数的优化 ,上面的博文链接讲的已经很清晰了,这里就不在赘述了。
KMP代码

    public int strStr1(String source, String target) {
        char[] s = source.toCharArray();
        char[] t = target.toCharArray();
        int m = s.length;
        int n = t.length;
        if (n == 0)
            return 0;
        int i = 0; // 主串的位置
        int j = 0; // 模式串的位置
        int[] next = getNext(target);
        while (i < m && j < n) {
            if (j == -1 || s[i] == t[j]) { // 当j为-1时,要移动的是i,当然j也要归0
                i++;
                j++;
            } else
                // i不需要回溯了
                // i = i - j + 1;
                j = next[j]; // j回到指定位置
        }
        if (j == n)
            return i - j;
        else
            return -1;
    }

    int[] getNext(String target) {
        char[] p = target.toCharArray();
        int[] next = new int[p.length];
        next[0] = -1;
        int j = 0;
        int k = -1;
        while (j < p.length - 1)
            if (k == -1 || p[j] == p[k]) {
                if (p[++j] == p[++k])  // 当两个字符相等时要跳过
                    next[j] = next[k];
                else
                    next[j] = k;
            } else
                k = next[k];
        return next;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值