Java&C++题解与拓展——leetcode1688.最大重复子字符串【序列DP、KMP】

82 阅读2分钟
每日一题做题记录,参考官方和三叶的题解

题目要求

image.png

image.png

思路一:序列DP

  • 一个很直观的动态规划思路,遍历整个sequencesequence找子串的个数;
  • 定义dp[i]dp[i]比较有趣,含义是以当前dp[i]dp[i]为结尾的子串能与模板wordword构成多少重复值,因为从前向后遍历,可以发现:【sequencesequence长度为nnwordword长度为mm
    • mm开始遍历,每次只比较最近一个mm长的子串是否重复,然后更新数据;
    • 更新数据在dp[im]dp[i-m]的基础上增加,即最近的不重合的部分,因为要求两次重复相连;
  • 过程中维护一个最大值即可。

Java

class Solution {
    public int maxRepeating(String sequence, String word) {
        int n = sequence.length(), m = word.length();
        if (n < m)
            return 0;
        int res = 0;
        int[] dp = new int[n + 10];
        for (int i = m; i <= n; i++) {
            String sub = sequence.substring(i - m, i);
            if (sub.equals(word)) {
                dp[i] = dp[i -m] + 1;
                res = Math.max(res, dp[i]);
            }
        }
        return res;
    }
}
  • 时间复杂度:O(m×n)O(m\times n)
  • 空间复杂度:O(n)O(n)

C++

class Solution {
public:
    int maxRepeating(string sequence, string word) {
        int n = sequence.size(), m = word.size();
        if (n < m)
            return 0;
        int res = 0;
        int dp[n + 10];
        memset(dp, 0, sizeof(dp));
        for (int i = m; i <= n; i++) {
            string sub = sequence.substr(i - m, m);
            if (sub == word) {
                dp[i] = dp[i -m] + 1;
                res = max(res, dp[i]);
            }
        }
        return res;
    }
};
  • 时间复杂度:O(m×n)O(m\times n)
  • 空间复杂度:O(n)O(n)

思路二:字符串哈希

  • 因为字符串的操作really昂贵且与长度有很大关系,数字操作相对好些,那么考虑用数字表示字符串——字符串哈希数组;
  • 具体就是乘一个很大的数,用一个较大范围的数表示不同的段;
    • 因为转换哈希的过程都是线性计算,所以通过加减即可得到对应小段的哈希表示;
    • 数组h[i]h[i]存放字符串第ii位字符的哈希值,是前续内容乘一个巨大PP值然后加上当前字母的编码大小;
    • 数组p[i]p[i]用来存放PiP^i便于计算,也可以不存每次都现场算,繁琐复杂一点;
    • PP要是一个质数,不然如果遇到某个字符是它的因数就不唯一了,大小要根据实际数据范围从小到大去尝试。
  • 构建好哈希值之后,就和上面一样了,只不过这里比较的是无符号数值,比取子串再比较简单得多。

Java

class Solution {
    public int maxRepeating(String sequence, String word) {
        int n = sequence.length(), m = word.length();
        if (n < m)
            return 0;
        // 预处理每个段的哈希表示
        String s = sequence + word;
        int P = 1313131, N = s.length();
        long[] h = new long[N + 10], p = new long[N + 10];
        p[0] = 1;
        for (int i = 1; i <= N; i++) {
            h[i] = h[i - 1] * P + s.charAt(i - 1);
            p[i] = p[i - 1] * P;
        }
        long hash = h[N] - h[N - m] * p[m]; // word的hash表示

        int res = 0;
        int[] dp = new int[n + 10];
        for (int i = m; i <= n; i++) {
            long cur = h[i] - h[i - m] * p[m];
            if (cur == hash) {
                dp[i] = dp[i -m] + 1;
                res = Math.max(res, dp[i]);
            }
        }
        return res;
    }
}
  • 时间复杂度:O(n+m)O(n+m)
  • 空间复杂度:O(n+m)O(n+m)

C++

  • 注意C++和java中数据类型的不同!
    • 因为是用很大的数转换字符串,所以报了overflow的错,然后改用了long long还是不行,去查了发现java里面都是无符号数,而C++默认有符号!那改成无符号数就ok啦~
class Solution {
public:
    int maxRepeating(string sequence, string word) {
        int n = sequence.size(), m = word.size();
        if (n < m)
            return 0;
        // 预处理每个段的哈希表示
        string s = sequence + word;
        int P = 1313131, N = s.size();
        unsigned long h [N + 10], p[N + 10];
        p[0] = 1;
        for (int i = 1; i <= N; i++) {
            h[i] = h[i - 1] * P + s[i - 1];
            p[i] = p[i - 1] * P;
        }
        unsigned long hash = h[N] - h[N - m] * p[m]; // word的hash表示

        int res = 0;
        int dp[n + 10];
        memset(dp, 0, sizeof(dp));
        for (int i = m; i <= n; i++) {
            unsigned long cur = h[i] - h[i - m] * p[m];
            if (cur == hash) {
                dp[i] = dp[i -m] + 1;
                res = max(res, dp[i]);
            }
        }
        return res;
    }
};
  • 时间复杂度:O(n+m)O(n+m)
  • 空间复杂度:O(n+m)O(n+m)

思路三:KMP

  • 复习一下前不久【不太久吧】学的KMP,重点在于根据模板找“备胎”数组nxtnxt,即失败了下一个如何跳转;
  • 然后基于这个就顺着找,和上面的思路类似:
    • 但循环需要从00开始,上面是回去找子串,但KMP不回溯,比较的仅是当前字符,然后调整jj指针【模板指针】的位置;【有点点绕】

Java

class Solution {
    public int maxRepeating(String sequence, String word) {
        int n = sequence.length(), m = word.length();
        if (n < m)
            return 0;
        // 模板跳转
        int[] nxt = new int[m];
        Arrays.fill(nxt, -1);
        for (int i = 1; i < m; i++) {
            int j = nxt[i - 1];
            while (j != -1 && word.charAt(j + 1) != word.charAt(i))
                j = nxt[j];
            if (word.charAt(j + 1) == word.charAt(i))
                nxt[i] = j + 1;
        }
        
        int[] dp = new int[n + 10];
        int j = -1;
        for (int i = 0; i < n; i++) {
            while (j != -1 && word.charAt(j + 1) != sequence.charAt(i))
                j = nxt[j];
            if (word.charAt(j + 1) == sequence.charAt(i)){
                j++;
                if (j == m - 1) { // 模板到尾
                    dp[i] = (i >= m ? dp[i - m] : 0) + 1;
                    j = nxt[j];
                }
            }
        }
        return Arrays.stream(dp).max().getAsInt();
    }
}
  • 时间复杂度:O(n+m)O(n+m)
  • 空间复杂度:O(n+m)O(n+m)

C++

class Solution {
public:
    int maxRepeating(string sequence, string word) {
        int n = sequence.size(), m = word.size();
        if (n < m)
            return 0;
        // 模板跳转
        int nxt[m];
        memset(nxt, -1, sizeof(nxt));
        for (int i = 1; i < m; i++) {
            int j = nxt[i - 1];
            while (j != -1 && word[j + 1] != word[i])
                j = nxt[j];
            if (word[j + 1] == word[i])
                nxt[i] = j + 1;
        }
        
        int dp[n + 10];
        memset(dp, 0, sizeof(dp));
        int j = -1, res = 0;
        for (int i = 0; i < n; i++) {
            while (j != -1 && word[j + 1] != sequence[i])
                j = nxt[j];
            if (word[j + 1] == sequence[i]){
                j++;
                if (j == m - 1) { // 模板到尾
                    dp[i] = (i >= m ? dp[i - m] : 0) + 1;
                    j = nxt[j];
                    res = max(res, dp[i]);
                }
            }
        }
        return res;
    }
};
  • 时间复杂度:O(n+m)O(n+m)
  • 空间复杂度:O(n+m)O(n+m)

思路四:枚举+内置函数【简单题的真实面貌】

  • 因为数据范围比较友好,所以可以直接暴力解决,那还不赶紧合理运用一些内置函数,暴力就一点脑子都不动!

Java

class Solution {
    public int maxRepeating(String sequence, String word) {
        int res = 0;
        while (sequence.contains(word.repeat(res))) {
            res++;
        }
        return res - 1;
    }
}
  • 时空复杂度取决于函数实现

C++

  • C++似乎是没有重复子串的函数,所以就手动加了
class Solution {
public:
    int maxRepeating(string sequence, string word) {
        int res = 0;
        string cur = word;
        while (sequence.find(cur) != -1) {
            res++;
            cur += word;
        }
        return res;
    }
};
  • 时空复杂度取决于函数实现

Rust

impl Solution {
    pub fn max_repeating(sequence: String, word: String) -> i32 {
        let mut res = 0;
        while sequence.contains(&word.repeat(res as usize)) {
            res += 1;
        }
        res - 1
    }
}
  • 时空复杂度取决于函数实现

总结

  • 力扣改了新界面,调整了一些图标位置
    • 对我有用:左右可伸缩幅度变大,控制台上下可调整大小、左右可换位置,题解查找更方便;
    • 么的大用:修改题目翻页位置,增加计时器……
    • 改了新的提交界面和题解界面不太习惯……
  • 复习K了一下KMP算法,意料之外地学了一些公式写法,OI Wiki真的太棒了~
  • Rust就随便写个内置函数版;
  • 难以想象这竟然是简单题。

欢迎指正与讨论!