LeetCode 28. 找出字符串中第一个匹配项的下标(KMP), 459.重复的子字符串

文章介绍了如何使用暴力匹配和KMP算法在给定字符串中查找第一个匹配项的下标。KMP算法的核心是前缀表,用于记录字符串的最长公共前后缀,从而避免不必要的回溯。此外,文章还展示了如何应用KMP算法来检测一个字符串是否由重复子串组成。
摘要由CSDN通过智能技术生成

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

题目链接:https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/

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

方法一:暴力匹配

  • 自己写的

主要思路还是以遍历haystack为中心,遇到第一个和needle相同的元素记录下当前的位置,然后同时遍历haystack和needle,如果有不同直接跳出,继续遍历到haystack的当前位置的下一个元素,依次循环即可
class Solution {
    public int strStr(String haystack, String needle) {
        int i = 0;
        while(i < haystack.length()){
            char c = needle.charAt(0);
            int j = 0;
            int temp = i;
            if(haystack.charAt(i) == c){
                while(j < needle.length() && i < haystack.length()){
                    if(haystack.charAt(i) != needle.charAt(j)){
                        break;
                    }
                    i++;
                    j++;
                    if(j == needle.length()){
                        return temp;
                    }
                }
            }
             i = temp + 1;
             j = 0;
        }
        return -1;
    }
}
  • 官方写的

代码中第8行的 i + m <= n 很巧妙,取了极端情况就是needle完全在haystack的最后面
hello ,llo 这种情况满足,其他情况就全满足、
第二个巧妙的地方就是11行的 i + j
class Solution {
    public int strStr(String haystack, String needle) {
        int n = haystack.length();
        int m = needle.length();
        char[] a = haystack.toCharArray();
        char[] b = needle.toCharArray();
        boolean flag = true;
        for(int i = 0; i + m <= n; i++){
            flag = true;
            for(int j = 0; j < m; j++){
                if(a[i + j] != b[j]){
                    flag = false;
                    break;
                }
            }
            if(flag){
                return i;
            }
        }
        return -1;
    }
}

KMP(算法解决的就是字符串匹配的问题)

  • 应用:KMP主要应用在字符串匹配上

  • 核心:KMP的主要思想是当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。****

所以如何记录已经匹配的文本内容,是KMP的重点,也是next数组肩负的重任

  • 前缀表

next数组就是一个前缀表(prefix table),前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。

前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。

前缀表的任务是当前位置匹配失败,找到之前已经匹配上的位置,再重新匹配,此也意味着在某个字符失配时,前缀表会告诉你下一步匹配中,模式串应该跳到哪个位置。

重点:

记录下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀

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

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

因为前缀表要求的就是相同前后缀的长度

**kmp算法整体流程**

https://code-thinking.cdn.bcebos.com/gifs/KMP%E7%B2%BE%E8%AE%B22.gif

i指向后缀末尾,j指向前缀末尾

j还代表这个i之前包括i子串的最长相等前后缀的长度

0 1 0 1 2 0
s : a a b a a f

j i

  1. 初始化:j = 0;(前缀开始位置)

next[0] = 0;(第一个元素的最长公共子串一定是0)

i 的初始化要从1开始,for(i = 1; i < s.length(); i++){ }

  1. 处理前后缀不相同的情况:s [ i ] != s [ j ]

s: a a b a a f
j i

此时前缀是aa,后缀是ab,此时j看前一位下标是0,就回退到0位置

j = next[j - 1]; 因为j - 1 ,所以 j > 0,

while( j > 0 && s [ i ] != s [ j ]) 回退一次很有可能s[ i ] 依然 != s[ j ]
j = next[j - 1]
  1. 前后缀相同情况

if(s[ i ] == s[ j ]){}

例子;起始时j指向0位置,i指向1位置 s[ i ] == s[ j ] ,让 j++,此时jj还代表这个i之前包括i子串的最长相等前后缀的长度(注意i的位置还没有变),此时更新next数组的值next[ i ] = j; 之后 i++(不用写,因为i++已经在for循环中++了)

填充next数组伪代码:重点看含义
void getNext(int[] next ,String s){
     int j = 0;
     next[0] = 0;
     for(int i = 0; i < s.length(); i++){
         while(j > 0 && s[i] != s[j]){
             j = next[j - 1];
         }
         if(s[i] == s[j]){
             j++;
         }
         next[i] = j;
     }    
}

得到的next数组就是完整的前缀表,没做任何移动,之后拿next数组原封不动的做模式串去匹配文本串

class Solution {
    //前缀表(不减一)Java实现
    public int strStr(String haystack, String needle) {
        if (needle.length() == 0) return 0;
        int[] next = new int[needle.length()];
        getNext(next, needle);

        int j = 0;
        for (int i = 0; i < haystack.length(); i++) {
            while (j > 0 && needle.charAt(j) != haystack.charAt(i)) 
                j = next[j - 1];
            if (needle.charAt(j) == haystack.charAt(i)) 
                j++;
            if (j == needle.length()) 
                return i - needle.length() + 1;
        }
        return -1;

    }
    
    private void getNext(int[] next, String s) {
        int j = 0;
        next[0] = 0;
        for (int i = 1; i < s.length(); 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; 
        }
    }
}
代码中为啥15 行那么写,举个例子就明白了
a b a a c
a a
最后结果i是指向3位置,况且一定是下面字符串先遍历完,所以i位置 - 下面字符串 + 1 就是要求的位置;
  • 自己根据理解写的

class Solution {
    public int strStr(String haystack, String needle) {
        int[] next = new int[needle.length()];
        getNext(next, needle);
        int j = 0;
        for(int i = 0; i < haystack.length(); i++){
            while(j > 0 && haystack.charAt(i) != needle.charAt(j)){
                j = next[j - 1];
            }
            if(haystack.charAt(i) == needle.charAt(j)){
                j++;
            }
            if(j == needle.length()){
                return i - needle.length() + 1;

            }
        }
        return -1;
    }

     public void getNext(int[] next, String s){
         int j = 0;
         next[0] = 0;
         for(int i = 1; i < s.length(); i++){
             while(j > 0 && s.charAt(i) != s.charAt(j)){
                 j = next[j - 1];
             }
             if(s.charAt(i) == s.charAt(j)){
                 j++;
             }
             next[i] = j;
         }
     }
}

459.重复的子字符串


题目链接:https://leetcode.cn/problems/repeated-substring-pattern/

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

方法一

当一个字符串s:abcabc,内部由重复的子串组成,那么这个字符串的结构一定是这样的:

也就是由前后相同的子串组成。

那么既然前面有相同的子串,后面有相同的子串,用 s + s,这样组成的字符串中,后面的子串做前串,前后的子串做后串,就一定还能组成一个s,如图:

所以判断字符串s是否由重复子串组成,只要两个s拼接在一起,里面还出现一个s的话,就说明是由重复子串组成。

当然,我们在判断 s + s 拼接的字符串里是否出现一个s的的时候,要刨除 s + s 的首字符和尾字符,这样避免在s+s中搜索出原来的s,我们要搜索的是中间拼接出来的s。

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

KMP方法

s: a b a b a b a b

0 0 1 2 3 4 5 6

next[len - 1] 这个位置代表最长相等前后缀,一定注意与上一道题想区分,上一道题是b 和 f 不一样时找前一个位置的next,这道题可不是冲突,直接就看next[len - 1]就代表了最长公共子串的长度;

也就是说长度上当前位置找,字符串比较冲突时,看前一个位置元素的next值找位置

class Solution {
    public boolean repeatedSubstringPattern(String s) {
        int[] next = new int[s.length()];
        getNext(next, s);
        int len = s.length();
        if(next[len - 1] != 0 && len % (len - (next[len - 1])) == 0){
            return true;
        }
        return false;
    }
    public void getNext(int[] next, String s){
        int j = 0;
        next[0] = 0;
        for(int i = 1; i < s.length(); i++){
            while(j > 0 && s.charAt(i) != s.charAt(j)){
                j = next[j - 1];
            }
            if(s.charAt(i) == s.charAt(j)){
                j++;
            }
            next[i] = j;
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值