代码随想录算法训练营第九天|28. 实现 strStr()、459.重复的子字符串

28. 实现 strStr()

在这里插入图片描述

题目链接:28. 实现 strStr()
文档讲解:代码随想录
状态:学过又忘了

思路: 可以暴力,可以kmp,也可以haystack.indexOf(needle);
暴力的时间复杂度O(m*n),kmp时间复杂度O(m+n)

KMP
KMP算法的核心在于next数组,next数组中存放的是 最长相同前缀和后缀的长度,也是在字符串不匹配时需要回退的步数。

构造next数组步骤:

  1. 初始化
    定义两个指针i和j,j指向前缀末尾位置,i指向后缀末尾位置。
    如a a b a a f,当i=2,j=1时,有 [a a b],最长前缀 a a(j指向这个a,是前缀末尾位置),最长后缀 a b(i指向b,是后缀末尾位置).
  2. 处理前后缀不相同的情况
    当前后缀不同时,j向前回退next[j-1]步,
    注意:每次回退完成后会重新比较位置 i 处字母是否匹配,如果不匹配还需要根据next[j-1]中的值再次回退,所以使用while循环
  3. 处理前后缀相同的情况
    如果前后缀相同,那么就同时向后移动i 和j 说明找到了相同的前后缀,同时还要将j(前缀的长度)赋给next[i], 因为next[i]要记录相同前后缀的长度。

本题题解:

    // KMP算法用于字符串搜索.haystack是文本串,needle是模式串
    public int strStr(String haystack, String needle) {
        // 将输入的字符串转换为字符数组
        char[] hChars = haystack.toCharArray();
        char[] nChars = needle.toCharArray();

        // 调用getNext方法生成模式串的`next数组,用于KMP搜索
        int[] next = getNext(nChars);

        // 初始化两个指针,i指向haystack的当前字符,j指向needle的当前字符
        int i = 0, j = 0;

        // 遍历haystack字符串
        while (i < hChars.length) {
            // 如果j大于0且当前字符不匹配,则根据next数组调整j的位置
            while (j > 0 && hChars[i] != nChars[j]) {
                j = next[j - 1];
            }

            // 如果当前字符匹配,则检查是否已经匹配完needle的所有字符
            if (hChars[i] == nChars[j]) {
                if (j == nChars.length - 1) {
                    // 如果匹配完needle,返回匹配的起始索引
                    return i - j;
                }
                // 否则,继续匹配下一个字符
                j++;
            }
            // 如果当前字符不匹配或者已经匹配完needle,则移动haystack的指针
            i++;
        }

        // 如果遍历完haystack都没有找到needle,则返回-1
        return -1;
    }

    // 生成next数组的方法
    public int[] getNext(char[] s) {
        // 初始化next数组,数组长度与s相同
        int[] next = new int[s.length];
        int i = 1, j = 0;//这里i必须从1开始(因为next[0]=0,i的更新和next数组的更新是同步的)

        // 遍历s,生成next数组
        while (i < s.length) {
            // 如果j大于0且当前字符不匹配,则根据next数组调整j的位置
            while (j > 0 && s[i] != s[j]) {
                j = next[j - 1];
            }

            // 如果当前字符匹配,则j加1
            if (s[i] == s[j]) {
                j++;
            }

            // 更新next数组,next[i]表示在s[0...i]中,s[i]字符的最长相同前缀的末尾位置
            next[i] = j;

            // 移动i指针
            i++;
        }

        // 返回生成的next数组
        return next;
    }

459.重复的子字符串

在这里插入图片描述

题目链接:459.重复的子字符串
文档讲解:代码随想录
状态:还可以

移动匹配题解:

    /**
     * 移动匹配
     * 判断字符串s是否由重复子串组成,只要两个s拼接在一起,里面还出现一个s的话,就说明是由重复子串组成。
     * 如s="abcabc",s+s="abc[abcabc]abc",中间包含abcabc
     * 当然,我们在判断 s + s 拼接的字符串里是否出现一个s的的时候,要刨除 s + s 的首字符和尾字符,这样避免在s+s中搜索出原来的s,我们要搜索的是中间拼接出来的s。
     *
     * @param s
     * @return
     */
    public boolean repeatedSubstringPattern(String s) {
        return new StringBuilder(s).append(s).substring(1, 2 * s.length() - 1).contains(s);
    }

KMP题解:
**KMP算法中的next数组:**在KMP算法中,next数组用于记录字符串s的每个位置i的最长相等前后缀长度。具体来说,next数组的第i个元素表示字符串s的前缀(从第一个字符到第i个字符)中最长的相等前后缀的长度。

例如,对于字符串"ababc",其next数组为[0, 0, 1, 2, 0],即:

next[0] = 0:前缀"a"没有相等的前后缀。
next[1] = 0:前缀"ab"没有相等的前后缀。
next[2] = 1:前缀"aba"的最长相等前后缀是"a",长度为1。
next[3] = 2:前缀"abab"的最长相等前后缀是"ab",长度为2。
next[4] = 0:前缀"ababc"没有相等的前后缀。

最长相等前后缀的长度: 在字符串的匹配过程中,next数组的最后一个元素next[len - 1]表示整个字符串的最长相等前后缀的长度。

判断字符串是否由重复子字符串构成: 使用next数组的最后一个元素,我们可以判断字符串是否由一个重复的子字符串构成。
具体步骤如下:
next[len - 1]表示字符串s的最长相等前后缀的长度。
如果next[len - 1] > 0,说明字符串有相等的前后缀。
如果字符串长度len能被len - next[len - 1]整除,说明字符串可以被分成若干个相同的子字符串。

为什么 len - next[len - 1] 是可能的子字符串长度?
考虑字符串s被最长的相等前后缀分成两部分:前缀和后缀。最长相等前后缀的长度为next[len - 1],表示字符串的末尾部分与开头部分是相同的。去掉这个相同的前缀和后缀,剩下的部分是一个最小的重复单位。

例如,字符串s = “abcabcabc”,其最长相等前后缀是"abcabc"。去掉这个相同的前缀和后缀,剩下的部分是"abc",这是最小的重复单位。整个字符串由三个"abc"组成。


	/**
     * 最长相等前后缀的长度为 next[len - 1]
     * 当 next[len - 1] > 0 && len % (len - next[len - 1] 则说明说明该字符串有重复的子字符串。
     */
    public boolean kmp(String s) {
        char[] chars = s.toCharArray();
        int[] next = new int[s.length()];
        int len = chars.length;
        int j = 0;
        // 遍历字符串中的字符
        for (int i = 1; i < len; i++) {
            // KMP算法:根据模式匹配更新 'j'
            while (j > 0 && chars[i] != chars[j]) {
                j = next[j - 1];
            }

            // 如果字符匹配,增加 'j'
            if (chars[i] == chars[j]) {
                j++;
            }

            // 将 'j' 的当前值存储在 'next' 数组中
            next[i] = j;
        }

        // 检查是否找到了重复的子串模式
        return next[len - 1] > 0 && len % (len - next[len - 1]) == 0;
    }
  • 16
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值