代码随想录-Day09 | LeetCode28. 找出字符串中第一个匹配项的下标、LeetCode459. 重复的子字符串

文档讲解: 代码随想录
视频讲解: 《代码随想录》算法公开课-跟着Carl学算法

KMP算法

KMP算法适合解决什么样的问题?主要应用在字符串的匹配上。我们在匹配两个字符串时,当出现不匹配的字母,KMP思想就是知道一部分之前已经匹配的子串,可以利用这些已知信息避免从头再去做匹配。由next数组来记录之前已经匹配好的子串,接下来如何创建next数组是重中之重!

next数组就是前缀表,前缀表有什么作用?我们重新明确KMP的思想:利用这些已知信息避免从头再去做匹配。 也就是说前缀表的任务是:当前位置匹配失败,利用前缀表找到之前已经匹配的位置,然后再重新匹配!这也就意味着一旦在某个字符匹配失败,前缀表会告诉我们下一步应该跳到什么位置。

明白作用之后,继续讨论下一个问题:前缀表数组里面到底存储的是什么?每个元素记录的是当前位置之前的子串中,最长相等前后缀的长度!

接下来简单解释什么是前缀?什么是后缀?

  • 前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串。
  • 后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串。

我们前缀表记录的是最长相等前后缀的长度,也就是要对比每个位置之前的子串,它所对应的前缀和后缀中相等子子串的长度!

为什么前缀表这样设计?我们的目的是利用它来告诉我们一旦匹配失败下一步跳到什么位置,通过前缀和后缀中相等的子子串,我们就可以找到下一步要调的位置,而这个位置正好就是最长相等前后缀的长度!

了解前缀表的原理之后,思考接下来的问题,也是KMP算法的重点,如何通过程序来获得这个next[]数组?

理解起来感觉比较困难,下面记录下一刷时的理解

构造next数组只要分为四个步骤:

  1. 数组的初始化
  2. 处理前后缀不同的情况
  3. 处理前后缀相同的情况
  4. 更新next数组的值

一、数组初始化
我们需要定义两个指针 i 和 j,其中 j 指向前缀末尾的位置(也是最长相等前后缀长度),i 指向后缀末尾的位置。给当前 j 赋初值 0,由于单个字母最长相等前后缀的为0,所以我们直接给next[0] 也赋值为0。指针 i 我们通过for循环进行常规遍历,由于next数组0索引位置的值已经被确定,所以这里 i 从1开始遍历。

二、处理前后缀不同的情况
当前 i 与 j位置处的字母不相同时,将j位置进行回退,回退到哪里取决于它next[j - 1],还不满足继续回退,因此需要用while循环,当然为了不超出数组范围,循环要加一条限定条件:j > 0。

三、处理前后缀相同的情况
当前 i 与 j位置处的字母相同时,将 j 位置+1,然后赋值给next[i],表示 i 位置之前子串的最长相等前后缀的长度!

LeetCode28. 找出字符串中第一个匹配项的下标

题目链接:28. 找出字符串中第一个匹配项的下标

题目描述:给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1 。

利用java自带的函数处理起来非常简单:

class Solution {
    public int strStr(String haystack, String needle) {
        if (haystack.contains(needle)) {
            return haystack.indexOf(needle);
        } else
            return -1;
    }
}

利用KMP算法求解:

class Solution {
    public int strStr(String haystack, String needle) {
        int[] next = getNext(needle); // 获取needle的前缀表
        // 遍历haystack字符串
        for (int i = 0, j = 0; i < haystack.length(); i++) {
            // 匹配不成功 j跳到相应位置
            while (j > 0 && haystack.charAt(i) != needle.charAt(j)) {
                j = next[j - 1];
            }
            // 匹配成功
            if (j < needle.length() && needle.charAt(j) == haystack.charAt(i)) {
                j++;
            }
            // 一旦j遍历到最后说明匹配成功
            if (j == needle.length()) {
                return i - needle.length() + 1;
            }
        }
        return -1;
    }

    // 先定义求next数组函数
    public int[] getNext(String s) {
        int[] next = new int[s.length()]; // 动态初始化
        int j = 0;
        next[0] = 0;

        for (int i = 1; i < s.length(); i++) {
            // 前后缀不相等情况
            while (s.charAt(j) != s.charAt(i) && j > 0) {
                j = next[j - 1];
            }
            // 前后缀相等情况
            if (s.charAt(j) == s.charAt(i)) {
                j++;
            }
            next[i] = j; // 更新i位置处的值
        }
        return next;
    }
}

LeetCode459. 重复的子字符串

题目链接:459. 重复的子字符串

题目描述:给定一个非空的字符串 s ,检查是否可以通过由它的一个子串重复多次构成。

移动匹配思路:判断字符串a是否由重复子串组成,只要将两个s前后拼接在一起,新的字符串里面如果还会有s出现,就说明可以由重复子串组成。在代码实现中,需要去除新字符串的首字符和尾字符,这能够在判断新字符串是否还有s时,避免搜索原本的s的。

class Solution {
    public boolean repeatedSubstringPattern(String s) {
        StringBuilder sb = new StringBuilder(s).append(s);
        String str = sb.subSequence(1, sb.length() - 1).toString(); // 掐头去尾
        if (str.contains(s)) {
            return true;
        } else {
            return false;
        }
    }
}
  • 17
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值