• 28. Implement strStr()


    题目:

    Implement strStr().

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

    Update (2014-11-02):
    The signature of the function had been updated to return the index instead of the pointer. If you still see your function signature returns a char * or String, please click the reload button  to reset your code definition.

    链接:  http://leetcode.com/problems/implement-strstr/

    题解:

    这道题虽然是easy难度,但解法很多,在一个美好的labor day下午让我头很大。可以有Brute force,KMP,Rabin-Karp,以及Suffix Array / Suffix Tree等等。 有关Suffix Tree真的要好好看一看,虽然现在还不懂,不过有种感觉这是解决String matching问题的终极武器(之一)。像MIT Advanced Data Structures里String那一课里大神Eric Demaine就讲得很清楚。要多看几遍。至于KMP,Rabin-Karp和Suffix Array可以看Princeton大神Sedgewick的课件和booksite。先占坑,根据学习进度一点一点补充各个解法。这些东西以前是unknown unknown, 现在是known unknown,要好好努力把他们变成known known。就像费德勒2016奔驰广告片一样, commitment, pushing your self further,faster,reaching ever high。

    下面先来看Brute force:

    从头开始暴力查找。  Time Complexity - O(m * n), Space Complexity - O(1).

    public class Solution {
        public int strStr(String haystack, String needle) {
            if(haystack == null || needle == null || haystack.length() < needle.length())
                return -1;
            if(needle.length() == 0)
                return 0;
            
            for(int i = 0; i <= haystack.length() - needle.length(); i++) {
                int j = 0;
                
                while(i + j < haystack.length() && j < needle.length() && haystack.charAt(i + j) == needle.charAt(j)) {
                    j++;
                    if(j == needle.length()) 
                        return i;
                }
            }
            
            return -1;
        }
    }

     

    KMP using DFA:

    先根据needle构建一个DFA,然后从haystack的第一个字母开始向后找,找到第一个occurrence或者遍历完haystack则循环结束。R代表alphabet字母表,或者Radix,既needle中distinct char的数量,对ASC II码我们可以简单假设为256。假如needle是Unicode则我们需要使用improved KMP,或者其他方法比如Boyer-Moore。 对于Multi String search,则可以使用Rabin-Karp。

    Time Complexity - O(n), Pre-process Time - O(m),Space Complexity - O(R * m)。

    public class Solution {
        private final static int R = 256;
        private int[][] dfa;
        
        public int strStr(String haystack, String needle) {
            if(haystack == null || needle == null || haystack.length() < needle.length())
                return -1;
            if(needle.length() == 0)
                return 0;
            int hsLen = haystack.length(), ndLen = needle.length();
            dfa = new int[R][ndLen];   
            buildDFA(needle);
            int i, j;
            
            for(i = 0, j = 0; i < haystack.length() && j < needle.length(); i++)
                j = dfa[haystack.charAt(i)][j];
            
            if(j == ndLen)
                return i - ndLen;
            else
                return -1;
        }
        
        private void buildDFA(String needle) {
            int rowNum = dfa.length, colNum = dfa[0].length;
            dfa[needle.charAt(0)][0] = 1;
            
            for(int x = 0, j = 1; j < colNum; j++) {    //x is state, j is col
                for(int c = 0; c < rowNum; c++)         //char
                    dfa[c][j] = dfa[c][x];              //copy mismatch cases
                dfa[needle.charAt(j)][j] = j + 1;       //set match cases
                x = dfa[needle.charAt(j)][x];           //update state x - restart case;
            }
        }
    }

    Improved KMP using NFA:

    改进版的KMP。 之前的例子因为R - radix的设置,所以可能导致ac时间反而比较慢,可以改进从而使用NFA,从而与pattern的alphabet无关,对Unicode都有效。构建next数组的部分很巧妙,要注意何时回溯。这段code自己也不太明白,看了很久。也不知道算是NFA还是DFA,有机会的话再来补充。

    Time Complexity - O(m + n), Space Complexity - O(m)

    public class Solution {
        private int[] next;
        
        public int strStr(String haystack, String needle) {
            if(haystack == null || needle == null || haystack.length() < needle.length())
                return -1;
            int m = needle.length(), n = haystack.length();    
            if(n == 0)
                return 0;
            buildNFA(needle);
            int i, j = 0;
            
            for(i = 0; i < n && j < m; i++) {
                while(j >= 0 && haystack.charAt(i) != needle.charAt(j))
                    j = next[j];
                j++;
            }
            
            if(j == m)
                return i - m;
            else
                return -1;
        }
        
        private void buildNFA(String needle) {
            int m = needle.length();
            next = new int[m];
            int j = -1;
            
            for(int i = 0; i < m; i++) {
                if(i == 0)                                              //initialize
                    next[i] = -1;                                   
                else if(needle.charAt(i) != needle.charAt(j))           //back-tracking
                    next[i] = j;
                else                                                    //copy equal cases
                    next[i] = next[j];
                    
                while(j >= 0 && needle.charAt(i) != needle.charAt(j))   //back-tracking
                    j = next[j];
                j++;
            }
        }
    }

    Boyer-Moore:

    (待补充)

    Rabin-Karp:

    使用Rolling-Hash。 最后会用到Monte carlo或者Las Vegas method。 Time Complexity - O(m * n), Space Complexity O(p)

    (待补充) 

    Suffix Array:  

    以O(n) Time Complexity构建Suffix Array (hard to implement),然后计算。

     (待补充)

    Suffix Tree:

    实际上是Suffix Trie。使用Ukkonen算法以O(n) Time complexity构建Suffix Tree,然后从root向下查找

     (待补充)

    --------------------------------------------------------------------------------------------华丽的分割线----------------------------------------------------------------------------------------------------------------------------------------------------------------

    二刷:

    String search经典问题。二刷要用KMP来完成。是时候彻底弄清楚KMP了。网上资料太多太杂,看了CMU,Stanford,Princeton,UBC等学校的lecture notes,每个implementation都不一样,网友都要被你们玩坏了。这样子看来其实KMP论文只是给了一种generalize的idea,像一个接口,每个人都可以根据这种idea自己来implement个性化的KMP实现。只要最后preprocessing以及search两部分能对上,就是一个正确的kmp实现。

    常用的一般有两种匹配方式:

    1. 求出对于每个字符,为了找出self-overlaps来决定我们可以skip多少重复的匹配,我们要find length of longest proper prefix and matching suffix。根据这个prefix table就可以在匹配的过程中skip重复匹配。这里主要参考了CMU的lecture notes以及youtube video。
      1. 建立prefix table。例子pattern = "ACACAGT", table= new int[pattern.length()]。 
          1. 这里proper prefix是指,假如当前字符为最后一个"A",它的prefix的集合 - A, AC, ACA, ACAC
          2. proper suffix是指, 假如当前字符为最后一个“A”, 第一个字符的后缀 -  A, CA, ACA, ACAC
          3. 从上面的例子可以看出最长长度为“ACA”的3,那么我们 table[4] = 3。我们依次求出上述pattern的prefix table每个值以后得到 table= [0, 0, 1, 2, 3, 0, 0] - 
        Pattern Prefix Suffix longest self-overlap j table
        A null null null 1 0
        AC A C null 2 0
        ACA A, AC A, CA A 3 1
        ACAC A, AC, ACA C, AC, CAC AC 4 2
        ACACA A, AC, ACA, ACAC A, CA, ACA, CACA ACA 5 3
        ACACAG A, AC, ACA, ACAC, ACACA G, AG, CAG, ACAG, CACAG null 6 0
        ACACAGT A, AC, ACA, ACAC, ACACA, ACACAG T, GT, AGT, CAGT, ACAGT, CACAGT null 7 0
        1. 下面是我们用来构建prefix table的逻辑和步骤:
          1. 根据pattern新建一个数组table,长度为m = pattern.length()
          2. table[0] = 0,这里我们没有prefix以及suffix,所以值为0
          3. 初始化i = 1, j = 0, 
          4. 在 i < m的情况下:
            1. pattern[i] == pattern[j]:
              1. table[i] = j + 1,    我们把之前求得的longest prefix长度 j ,加 1,然后赋给 table[i]
              2. i++,   增加i来计算下一个字符
            2. str[i] != str[j]:
              1. 假如j > 0, 那么设置j = table[j - 1], 这里我们对 j 进行回溯。 下一步查看之前是否能有 pattern[j] == pattern[i]
              2. 假如 j = 0,那么说明没有match,
                1. 我们设置 table[i] = 0
                2. i++, 增加i来计算下一个字符
      2. Search。 Search的过程跟构建prefix table基本一样。稍有不同的地方,就是从遍历pattern变为了遍历text,并且多了一个if语句判断找到了第一个全匹配之后如何继续操作
        1. 初始化i = 0, j = 0
        2. n = text.length(),  m = pattern.length();
        3. 在 i < n的情况下:
          1. pattern[j] == text[i]:
            1. 假如j == m - 1,这时候找到全匹配, 我们可以return这时候串的开头index:   i - m + 1;
            2. 否则我们进行 i++, j++,继续尝试匹配当前串的下一个字符
          2. pattern[j] != text[i]:
            1. 假如 j > 0, 那么我们设置 j = table[j - 1], 对j进行回溯
            2. 否则我们进行 i++, 没有任何匹配,我们要从text的下一个位置开始重新匹配。
    2. 求出 KMP的next数组,根据next数组匹配。这里这个next数组其实是一个NFA,可以在匹配失败的时候用来进行回溯。利用这个next数组也可以进行匹配。 这里主要参考了Princeton的lecture notes和code,里面有些步骤还不是很懂,先步骤记录一下。
      1. Preprocess:
        1. 设置m = pattern.length(), int[] next = new int[m]
        2. 设置j = -1
        3. 在i = 0; i < m; i++的情况下:
          1. i == 0,  设置next[i] = -1
          2. 假如 pattern.charAt(i) != pattern.charAt(j), 设置next[i] = j
          3. 否则 pattern.charAt(i) == pattern.charAt(j), next[i] = next[j]
          4. while j >= 0并且 pattern.charAt(i) != pattern.charAt(j), 我们对j进行迭代回溯,  j = next[j]
          5. j++
      2. Search:
        1. 定义int i, j。  m = pattern.length(), n = text.length()
        2. 初始条件i = 0, j = 0, 当 i < n && j < m时:
          1. while j >= 0 并且 text.charAt(i) != pattern.charAt(j)时, 对j进行迭代回溯, j = next[j]
          2. j++
        3. 循环结束后,假如j == m, return i - m 为第一个match pattern的子串头index
        4. 否则return -1

    Java:

    KMP - using prefix table:

    Time Complexity - O(m + n), Space Complexity - O(m)

    public class Solution {
        public int strStr(String haystack, String needle) {
            if (haystack == null || needle == null || haystack.length() < needle.length()) {
                return -1;
            }
            if (needle.equals("")) {
                return 0;
            }
            int[] prefixTable = preProcess(needle);
            int i = 0, j = 0;
            int m = needle.length(), n = haystack.length();
            while (i < n) {                                         // KMP Search 
                if (haystack.charAt(i) == needle.charAt(j)) {
                    if (j == m - 1) {
                        return i - m + 1;
                    }
                    i++;
                    j++;
                } else if (j > 0) {
                    j = prefixTable[j - 1];
                } else {
                    i++;
                }
            }
            return -1;
        }
        
        private int[] preProcess(String pattern) {  // KMP: building prefix table
            int m = pattern.length();
            int[] prefixTable = new int[m];
            int i = 1, j = 0;
            while (i < m) {
                if (pattern.charAt(i) == pattern.charAt(j)) {
                    prefixTable[i] = j + 1;
                    i++;
                    j++;
                } else if (j > 0) {
                    j = prefixTable[j - 1];
                } else {
                    prefixTable[i] = 0;
                    i++;
                }
            }
            return prefixTable;
        }
    }

     

    KMP - build KMP NFA using next array:

    public class Solution {
        public int strStr(String haystack, String needle) {
            if (haystack == null || needle == null || haystack.length() < needle.length()) {
                return -1;
            }
            if (needle.equals("")) {
                return 0;
            }
            int[] next = preprocess(needle);
            int m = needle.length(), n = haystack.length();
            int i, j;
            for (i = 0, j = 0; i < haystack.length() && j < needle.length(); i++) {
                while (j >= 0 && haystack.charAt(i) != needle.charAt(j)) {
                    j = next[j];
                }
                j++;
            }
            if (j == m) {
                return i - m;
            }
            return -1;
        }
        
        private int[] preprocess(String pattern) {  // KMP build next array
            int m = pattern.length();
            int next[] = new int[m];
            int j = -1;
            for (int i = 0; i < m; i++) {
                if (i == 0) {
                    next[i] = -1;
                } else if (pattern.charAt(i) != pattern.charAt(j)) {
                    next[i] = j;
                } else {
                    next[i] = next[j];
                }
                while (j >= 0 && pattern.charAt(i) != pattern.charAt(j)) {   // here we continue backtracking j
                    j = next[j];
                }
                j++;
            }
            return next;
        }
    }

    Reference:

    http://www.cs.cmu.edu/~ab/211/lectures/

    http://www.cs.cmu.edu/~ab/211/lectures/Lecture%2018%20-%20String%20Matching-KMP.ppt

    https://www.youtube.com/watch?v=5i7oKodCRJo

    http://algs4.cs.princeton.edu/53substring/

    http://programmerspatch.blogspot.com/2013/02/ukkonens-suffix-tree-algorithm.html

    http://web.stanford.edu/~mjkay/gusfield.pdf

    http://www.cs.cmu.edu/~avrim/451/lectures/lect1121.pdf

    https://www.topcoder.com/community/data-science/data-science-tutorials/introduction-to-string-searching-algorithms/

    http://www.geeksforgeeks.org/searching-for-patterns-set-2-kmp-algorithm/

    https://en.wikipedia.org/wiki/Rabin%E2%80%93Karp_algorithm

    http://www.inf.fh-flensburg.de/lang/algorithmen/pattern/kmpen.htm

    https://web.stanford.edu/class/cs97si/10-string-algorithms.pdf

    https://www.cs.princeton.edu/~rs/AlgsDS07/21PatternMatching.pdf

    http://www-igm.univ-mlv.fr/~lecroq/string/node8.html

    http://www.ics.uci.edu/~eppstein/161/960227.html

    http://jakeboxer.com/blog/2009/12/13/the-knuth-morris-pratt-algorithm-in-my-own-words/ 

  • 相关阅读:
    112、TensorFlow初始化变量
    111、TensorFlow 初始化变量
    110、TensorFlow张量值的计算
    109、TensorFlow计算张量的值
    108、TensorFlow 类型转换
    107、TensorFlow变量(三)
    106、TensorFlow变量 (二) reshape
    105、TensorFlow的变量(一)
    104、Tensorflow 的变量重用
    103、Linux 编译 Kaldi 语音识别工具
  • 原文地址:https://www.cnblogs.com/yrbbest/p/4435074.html
Copyright © 2020-2023  润新知