hash 值重复_“重复”相关的问题

重复子串比较的核心是使用 Rabin-Karp (Rolling Hash)。

Rabin-Karp字符串编码的本质是对字符串进行哈希,将字符串之间的比较转化为编码之间的比较

有N个不同的字符,可以将字符组成的串,映射成 N进制表示的10进制数。每一个数可以代表一种字符。 去头加尾的哈希计算可以在

的时间内完成

题目A(困难)

1044. 最长重复子串
给出一个字符串 S,考虑其所有 重复子串S 的连续子串,出现两次或多次,可能会有重叠)。
返回 任何具有最长可能长度的重复子串。(如果 S 不含重复子串,那么答案为 ""。) 示例 1: 输入:"banana" 输出:"ana" 示例 2: 输入:"abcd" 输出:""

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

  1. 从 1 到 N 中选取子串的长度 L
  2. 检查字符串中是否存在长度为 L 的重复子串

子任务一:二分查找

如果字符串中存在长度为 L 的重复子串,那么一定存在长度为 K < L 的重复子串(选取长度为 L 的重复子串的某个长度为 K 的子串即可),因此我们可以使用二分查找的方法,找到最大的 L。

子任务二:是否存在长度为L的重复子串(Rabin-Karp 字符串编)

使用 Rabin-Karp 算法将字符串进行编码,这样只要有两个编码相同,就说明存在重复子串。

对于选取的长度 L:使用长度为 L 的滑动窗口在长度为 N 的字符串上从左向右滑动;

51ecc7ec6c7bbc8db8fa1d20b0199b9d.png
哈希计算公式

字符一共有26,a最小可以取26.

4a65a719f399eb3c703360950ede5982.png

检查当前处于滑动窗口中的子串的编码是否已经出现过(用一个集合存储已经出现过的编码)。若已经出现过,就说明找到了长度为 L 的重复子串;若没有出现过,就把当前子串的编码加入到集合中。

注意事项:取模

最后一个需要解决的问题是,在实际的编码计算中,hash值、

可能会非常大,在 C++ 和 Java 语言中,会导致整数的上溢出。所以,需要对编码值进行取模,将编码控制在一定的范围内,防止溢出,即h = h % modulus。
h = (h * a - nums[start - 1] * aL % modulus + modulus) % modulus;
h = (h + nums[start + L - 1]) % modulus;

注意事项2:哈希冲突

取模会导致哈希冲突,既是有可能两个字符串是不同的,但是哈希值相同。后续的话学习一下hash冲突的解决方式。

最终代码

public int search(int L, int n, int[] nums) {
        // roll hash的base值,26个字符,用26进制玩。
        int a = 26;

        // 防止hash值溢出,使用的mod数
        long modulus = (long) Math.pow(10, 16);

        // 计算长度为L的第一个子串的 roll hash 值
        long h = 0;
        for (int i = 0; i < L; ++i)
            h = (h * a + nums[i]) % modulus;

        // 存储已出现过的hash值
        HashSet<Long> seen = new HashSet<>();
        seen.add(h);

        // 第一位的26的L幂 取模 : aL % modulus
        long aL = 1;
        for (int i = 1; i <= L; ++i)
            aL = (aL * a) % modulus;

        for (int start = 1; start < n - L + 1; ++start) {
            // compute rolling hash in O(1) time
            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();
        // 将字符串映射成 hash值 (26进制的数值)
        // 实现常数时间复杂度的滑动窗口
        int[] nums = new int[n];
        for (int i = 0; i < n; ++i)
            nums[i] = (int) S.charAt(i) - (int) 'a';

        // 二分搜索, L = 要定位的重复字符串的长度
        int minRepeat = 1, maxRepeat = n;
        int L;
        while (minRepeat != maxRepeat) {
            L = minRepeat + (maxRepeat - minRepeat) / 2;

            if (search(L, n, nums) != -1)
                minRepeat = L + 1;
            else
                maxRepeat = L;
        }

        int start = search(minRepeat - 1, n, nums);
        return start != -1 ? S.substring(start, start + minRepeat - 1) : "";
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值