• 【字符串匹配】滚动哈希


    LeetCode 28. Implement strStr()

    题目描述

    Implement strStr().

    Return the index of the first occurrence of needle in haystack, or -1 if needle is not part of haystack.

    Clarification:

    What should we return when needle is an empty string? This is a great question to ask during an interview.

    For the purpose of this problem, we will return 0 when needle is an empty string. This is consistent to C's strstr() and Java's indexOf().

    Example 1:

    Input: haystack = "hello", needle = "ll"
    Output: 2

    Example 2:

    Input: haystack = "aaaaa", needle = "bba"
    Output: -1

    Example 3:

    Input: haystack = "", needle = ""
    Output: 0

    Constraints:

    • 0 <= haystack.length, needle.length <= 5 * 104
    • haystack and needle consist of only lower-case English characters.

    解题思路

    一道基础的字符串匹配的题目。

    这道题设定的难度是 Easy,所以简单的两层循环暴力算法也能通过,事件复杂度 O(M*N)

    但是这种算法显然时间复杂度比较高,如果设定为 Hard 的话就无法通过了。

    我们希望能用的是线性时间的字符串匹配算法,常见的有 KMP、BM(Boyer Moore)、Sunday 算法等。KMP 算法是教科书上的经典算法,但是比较晦涩,手写记忆都比较麻烦,面试中几乎不会用到这一算法;BM 算法对 KMP 进行了改进,性能有数倍提升,文本编辑器和IDE中常用的查找功能就是基于BM算法。

    有一种简单好记好理解的算法,是基于哈希对暴力算法的改进,这种算法叫 Rabin-Karp 算法,主要用于检测文章抄袭。两层循环的低效是因为每次移动一位,都需要从头重新比较两个串,所以开销是 M*N。这里我们通过一种策略,复用上一次的比较结果来进行这一次比较。这种办法的本质是把字符串看作一个 k 进制数,然后在滑动窗口里计算这个 k 进制数在窗口内部分的数值是否与待匹配值相等。这样每次比较的时间复杂度降低到 O(1),总时间降低到 O(M+N)

    这里有一篇博客对以上5种字符串匹配算法进行了介绍和比较 字符串匹配常见算法(BF,RK,KMP,BM,Sunday)

    参考代码

    需要注意的点:

    • 乘法的溢出问题,步步取模
    • 哈希值用有符号整数,因为减法会导致出现负数
    /*
     * @lc app=leetcode id=28 lang=cpp
     *
     * [28] Implement strStr()
     */
    
    // @lc code=start
    class Solution {
    public:
        int64_t R_n(int R, int nl, int MOD) {
            int64_t Rn = 1;
            int64_t base = R;
            while(nl) {
                if (nl & 1) {
                    Rn = (Rn * base) % MOD;
                }
                nl >>= 1;
                base = (base * base) % MOD;
            }
            return Rn;
        }
        int strStr(string haystack, string needle) {
            if (needle.size() == 0) return 0; // !!
            int hl = haystack.size();
            int nl = needle.size();
            constexpr int MOD = 1e9+7;
            constexpr int R = 26;
            // const int64_t Rn = (int64_t)pow(R, nl) % MOD;
            const int64_t Rn = R_n(R, nl, MOD);
            int64_t ns = 0;
            int64_t hs = 0; // must be signed, for hs may < 0
            for (int i=0; i<nl; i++) {
                ns = (ns * R + (needle[i] - 'a')) % MOD;
                hs = (hs * R + (haystack[i] - 'a')) % MOD;
            }
            if (hs == ns) return 0;
            for (int i=nl; i<hl; i++) {
                hs = (hs * R + (haystack[i] - 'a') - (haystack[i-nl] - 'a') * Rn) % MOD;
                hs = (hs + MOD) % MOD;
                if (hs == ns) return i-nl+1;
            }
            return -1;
        }
    };
    // @lc code=end
    

    扩展解读 Rabin-Karp 算法

    实际上不只是右移一位,对于左移一位、左端或右端加长一位或是缩短一位的情况,RK 算法也能进行类似的处理。有 一个B站视频 专门讲 Rolling Hash 的,涉及到了以下几道题:

    这篇 novoland.github.io 的博客 不仅介绍了 RK 算法,还讲解了 Java 中使用的 Hash 算法,以及 RK 算法的二位扩展。

    彩蛋

    常用于取模的质数:1e9+7, 19260817, 19491001 ……

  • 相关阅读:
    Codeforces Round #535 (Div. 3) 1108C
    Codeforces Round #536 (Div. 2) B. Lunar New Year and Food Ordering
    Leetcode--136. Single Number(easy)
    Leetcode--572. Subtree of Another Tree(easy)
    Leetcode--101. Symmetric Tree(easy)
    Leetcode--680. Valid Palindrome II(easy)
    2017百度之星资格赛 1003 度度熊与邪恶大魔王 背包DP
    台州 OJ 1704 Cheapest Palindrome 回文 区间DP
    洛谷 P1019 单词接龙 深搜
    UVA 11882 Biggest Number 深搜 剪枝
  • 原文地址:https://www.cnblogs.com/zhcpku/p/14696570.html
Copyright © 2020-2023  润新知