【代码训练营】day9 | 28. 实现 strStr() & 459.重复的子字符串 & KMP

65 阅读2分钟

所有代码 java

实现strStr() LeetCode 28

题目链接:实现strStr() LeetCode 28 - 中等

思路

首选暴力,然后是字符串的匹配题经典的KMP算法(这个不太会,可以看代码随想录)

暴力解法

public int strStr(String haystack, String needle) {
    int n = haystack.length();
    int m = needle.length();
    // 原串开始进行匹配
    for (int i = 0; i <= n - m; i++) {
        int a = i; // 原串匹配位置
        int b = 0; // 子串匹配位置
        while (b < m && haystack.charAt(a) == needle.charAt(b)){
            // 匹配成功往后移匹配下一个字母
            a++;
            b++;
        }
        // 子串完全匹配成功
        if (b == m) {
            return i;
        }
    }
    // 没有匹配成功
    return -1;
}

KMP算法
KMP算法最关键的是维护一个next的数组,这个next数组存的就是每次匹配开始的下标,有了这个next数组每次匹配就不用从头开始,极大的节省了时间。

求next数组分为四步

  1. next数组初始化;
  2. 前后缀相同的情况处理逻辑;
  3. 前后缀不同的情况处理逻辑;
  4. next数组更新
public int strStr(String haystack, String needle) {
    if (needle.length() == 0) return 0;
    int[] next = new int[needle.length()];
    getNext(next,needle);

    // j 指向子串,i指向模式串
    int j = 0;
    for (int i = 0; i < haystack.length(); i++) {
        // 该项未匹配成功就找j的前项 最长相等前缀的位置
        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){
    // j指向前缀末尾位置,也是最长相同前后缀的长度
    int j = 0;
    next[0] = 0;
    // i指向后缀末尾位置
    for (int i = 1; i < s.length(); i++) {
        while (j > 0 && s.charAt(i) != s.charAt(j)){
            // next[j]记录的是最长相同的前后缀
            j = next[j-1];
        }
        if (s.charAt(i) == s.charAt(j)){
            j++;
        }
        // 更新next数组
        next[i] = j;
    }
}

总结

暴力简单,时间复杂度要高一点O(n*m),实在不会用暴力能过一点就一点了。但是KMP时间复杂度只有O(m+n),自己还需要深入的理解,多看几遍。

重复的子字符串 LeetCode 459

题目链接:重复的子字符串 LeetCode 459 - 简单

思路

首先想到的也是暴力,其次是KMP

暴力

    public boolean repeatedSubstringPattern(String s) {
        StringBuilder sb = new StringBuilder();
        int len = s.length();
        // 字符一定是从第一个字母开始, 且超过一半就不会是子串了 - 剪枝
        for (int i = 0; i < len / 2; i++){
            sb.append(s.charAt(i));
            // 取的子串并不能被整除,就不能构成子串 - 剪枝
            if (len % sb.length() != 0){
                continue;
            }else {
                int div = len / sb.length();
                StringBuilder sc = new StringBuilder();
                // 判断要重复几次
                for (int j = 0; j < div; j++) {
                    sc.append(sb);
                }
//                System.out.println(sc.toString() == s); 字符串的==判断的是地址,需用equal
                if (sc.toString().equals(s)){
                    return true;
                }
            }
        }
        return false;
    }

KMP

难!!!

public boolean repeatedSubstringPattern(String s) {
    if (s.equals("")) return false;

    int len = s.length();
    char[] chars = s.toCharArray();
    int[] next = new int[len]; // 初始所有元素为0

    // 构造 next 数组过程,j从0开始(空格),i从2开始
    int j = 0;
    for (int i = 1; i < len; i++) {
        // 匹配不成功,j回到前一位置 next 数组所对应的值
        while (j>0 && chars[i] != chars[j]){
            j = next[j-1];
        }
        // 匹配成功,j往后移
        if (chars[i] == chars[j]){
            j++;
        }
        // 更新 next 数组
        next[i] = j;
    }

    // 最后判断是否是重复的子字符串,
    // 这里next[len-1]即代表next数组末尾的值
    // len % (len - next[len-1] 表示重复了几次,并且能整除
    if (next[len - 1] > 0 && len % (len - next[len - 1]) == 0) {
        return true;
    }
    return false;
}

总结

我们还可以把字符串重复一遍,再到其中搜索去除首尾字母能不能找到原字符串,找到则是重复构成的。如:abcabc,重复一遍abcabcabcabc,中间一定有原字符串,注意:要去掉首尾的字母,不然就找到自己了!漂亮的一行写完

    return (s+s).indexOf(s,1) != s.length();