Fork me on GitHub

字符串单模式匹配 暴力+哈希

字符串匹配问题是很经典的问题,在此详细记录一下各种方法。(Java实现)LeetCode28

给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回  -1。(字符为小写)

 

一,BF算法(暴力)

//1.substring
class
Solution { public int strStr(String haystack, String needle) { int h = haystack.length(); int n = needle.length(); for (int i = 0; i <= h - n; i++) { if (haystack.substring(i, i + n).equals(needle)) return i; } return -1; } }

 

//2.indexOf
public int strStr(String haystack, String needle) {
        return haystack.indexOf(needle);
}

/**
  indexOf源码:(找到首字母后双指针)
   * @param   source       the characters being searched.      haystack底层数组
     * @param   sourceOffset offset of the source string.       0
     * @param   sourceCount  count of the source string.       haystack.length()
     * @param   target       the characters being searched for.   needle
     * @param   targetOffset offset of the target string.      0
     * @param   targetCount  count of the target string.       needle.length()
     * @param   fromIndex    the index to begin searching from.   0 
*/
//先在source中找到首字母与target相同的index。再用双指针判定,如果指到末尾则完全匹配,返回索引,
//否则回溯source下一个字符。(
只保留重要部分)
static int indexOf(char[] source, int sourceOffset, int sourceCount,
            char[] target, int targetOffset, int targetCount,
            int fromIndex) {char first = target[targetOffset];
        int max = sourceOffset + (sourceCount - targetCount);

        for (int i = sourceOffset + fromIndex; i <= max; i++) {
            /* Look for first character. */
            if (source[i] != first) {
                while (++i <= max && source[i] != first);
            }

            /* Found first character, now look at the rest of v2 */
            if (i <= max) {
                int j = i + 1;
                int end = j + targetCount - 1;
                for (int k = targetOffset + 1; j < end && source[j]
                        == target[k]; j++, k++);

                if (j == end) {
                    /* Found whole string. */
                    return i - sourceOffset;
                }
            }
        }
        return -1;
    }

 

二,RK算法(哈希)

RK算法的全称叫 Rabin-Karp 算法,是由它的两位发明者 Rabin 和 Karp 的名字来全名的。

 因为字符为小写,可以将主串转换成0~25的数字序列。遍历子串(长度为L)的L个数计算哈希值Hash(needle),再遍历主串(长度为M),

将主串分为多个长为L的模式串,对所有模式串取哈希,边取哈希边与Hash(needle)比较。为了快速对模式串取哈希,利用滑动窗口的特性,每次滑动都有一个元素进,一个出。

按照将字符映射数字的方式,abcd 整数数组形式就是 [0, 1, 2, 3],转换公式为:h0=0*263+1*262+2*261+3*260。即按从高位到地位的顺序,将其表示成26进制。

由十进制类比可得,该方式取的哈希值不会产生哈希冲突,因为一个数值用指定的进制只有一种表示方法。

下面来考虑窗口从 abcd 滑动到 bcde 的情况。这时候模式串从 [0, 1, 2, 3] 变成了 [1, 2, 3, 4],数组最左边的 0 被移除,同时最右边新添了 4。

滑动后数组的哈希值可以根据滑动前数组的哈希值来计算,计算公式如下所示。h1=(h0-0*263)*26+4*260;写成通式如下所示:h1=(h0*26−c[0]*26L)+c[L]。

(c为主串数组,L为子串长度)。即Hash(i+1)=(Hash(i)-c[i]*26L-1)*26+c[L+i]*260

如何避免溢出?  

L为8的时候,26L-1溢出。因此需要设置数值上限来避免溢出。设置数值上限可以用取模的方式,即用 h % modulus 来代替原本的哈希值。理论上,

modules 应该取一个很大数,对于这个问题来说 231足够了。

计算子字符串 haystack.substring(0, L) 和 needle.substring(0, L) 的哈希值。从起始位置开始遍历:从第一个字符遍历到第 N - L 个字符。

根据前一个哈希值计算滚动哈希。如果子字符串哈希值与 needle 字符串哈希值相等,返回滑动窗口起始位置。返回 -1,这时候 haystack 字符串中不存在 needle 字符串。

class Solution {
  // function to convert character to integer
  public int charToInt(int idx, String s) {
    return (int)s.charAt(idx) - (int)'a';
  }

  public int strStr(String haystack, String needle) {
    int L = needle.length(), n = haystack.length();
    if (L > n) return -1;

    // base value for the rolling hash function
    int a = 26;
    // modulus value for the rolling hash function to avoid overflow
    long modulus = (long)Math.pow(2, 31);

    // compute the hash of strings haystack[:L], needle[:L]
    long h = 0, ref_h = 0;
    for (int i = 0; i < L; ++i) {
      h = (h * a + charToInt(i, haystack)) % modulus;
      ref_h = (ref_h * a + charToInt(i, needle)) % modulus;
    }
    if (h == ref_h) return 0;

    // const value to be used often : a**L % 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 - charToInt(start - 1, haystack) * aL
              + charToInt(start + L - 1, haystack)) % modulus;
      if (h == ref_h) return start;
    }
    return -1;
  }
}

 参考链接

posted @ 2020-06-18 11:11  Faded828x  阅读(180)  评论(0编辑  收藏  举报