LeetCode: 最长重复子串

参考网站:https://leetcode-cn.com/problems/longest-duplicate-substring/

1. 题目描述

给出一个字符串 S,考虑其所有重复子串(S 的连续子串,出现两次或多次,可能会有重叠)。
返回任何具有最长可能长度的重复子串。(如果 S 不含重复子串,那么答案为 “”。)

示例 1:

输入:“banana”
输出:“ana”

示例 2:

输入:“abcd”
输出:""

提示:

2 <= S.length <= 10^5
S 由小写英文字母组成。

2. 关键词

二分查找,Rabin-Karp 字符串编码

3. 思路
  1. 我们最能想到的方法是:找出所有可能的字符串情况,由于是找最长子串,那么可以从长往短找,然后进行使用滑动窗口与所有可能的字符串逐个对比。但是这种时间复杂度很高为O(n^3),
  2. 优化思路:如何尽可能快地找到最长字符串(二分查找);如何快速匹配相同字符串(Rabin-Karp 字符串编码)。
  3. 原理:
  • 假设符合条件的最长重复字符串的长度为L,那么一定存在其子串长度为L0(L0<L)也符合条件,那么也一定不存在长度为L2(L2>L)的字符串符合条件( 不然与已知条件冲突)
  • 我们可以使用 Rabin-Karp 算法将整个字符串进行编码,这样只要有两个编码相同,就说明存在重复子串。而无需遍历两个字符串挨个字符对比。
  1. 具体操作:见代码
4. 代码(Java)
class Solution {
    /*
   使用滑动窗口Rabin-Karp。搜索至少发生2次的给定长度的子字符串。
   如果子字符串退出,则返回开始位置,否则返回-1。
        */
    public int search(int L, int a, long modulus, int n, int[] nums) {
        // 计算字符串S[:L]的散列
        long h = 0;
        for (int i = 0; i < L; ++i) h = (h * a + nums[i]) % modulus;
        // 已经看到长度为L的字符串的散列
        HashSet<Long> seen = new HashSet();
        seen.add(h);
        // 经常使用的值:a**L%模数
        long aL = 1;
        for (int i = 1; i <= L; ++i) aL = (aL * a) % modulus;

        for (int start = 1; start < n - L + 1; ++start) {
            // 在O(1)时间内计算滚动散列
            h = (h * a - nums[start - 1] * aL % modulus + modulus) % modulus;
            h = (h + nums[start + L - 1]) % modulus;
            if (seen.contains(h)) return start;
            seen.add(h);
        }
        return -1;
    }

    public String longestDupSubstring(String S) {
        int n = S.length();
        // 将字符串转换为整数数组
        // 实现恒定时间片
        int[] nums = new int[n];
        for (int i = 0; i < n; ++i) nums[i] = (int)S.charAt(i) - (int)'a';
        // 滚动散列函数的基值
        int a = 26;
        // modulus value for the rolling hash function to avoid overflow
        long modulus = (long)Math.pow(2, 32);
        // 二进制搜索,L=重复字符串长度
        int left = 1, right = n;
        int L;
        while (left != right) {
            L = left + (right - left) / 2;
            if (search(L, a, modulus, n, nums) != -1) left = L + 1;
            else right = L;
        }

        int start = search(left - 1, a, modulus, n, nums);
        return start != -1 ? S.substring(start, start + left - 1) : "";
    }
}
5. 时间及空间复杂度

时间复杂度:时间复杂度:O(NlogN),二分查找的时间复杂度为O(logN),Rabin-Karp 字符串编码的时间复杂度为O(N)。

空间复杂度:O(N),用来存储字符串编码的集合。

发布了82 篇原创文章 · 获赞 0 · 访问量 870

猜你喜欢

转载自blog.csdn.net/weixin_43518038/article/details/105158361