KMP 算法

function totalStrStr(haystack, needle) {
        const n = haystack.length;
        const m = needle.length;
        if (m === 0) {
            return 0;
        }
        const pi = new Array(m).fill(0);

        /**
         * 下面开始求解 needle 的 pi(前缀函数)
         **/

        // i=0 的时候, 字符串长度为1,则没有真前后缀,所以 pi[0] = 0,i 直接从1开始求 pi
        // 这里 j 初始化为 pi[i-1] 的值,也就是上一轮pi的值,因为 i 从0开始,所以初始化为上一轮的 pi值: 0
        for (let i = 1, j = 0; i < m; i++) {
            // 此处的步骤可以省略,因为每轮迭代后,j 保留着每轮的 pi 值,在下一轮迭代中 j 就已经是 pi[i-1] 了
            // j=pi[i-1];

            // 前缀函数的重要性质:求解第 i+1 个字符的 pi值,只依赖两个参数 j 、 pi[0:j-1]
            // 换个说法就是,只依赖上一轮的 pi值、needle 的前面的子串 needle[0:j-1] 的前缀函数
            while (j > 0 && needle[i] !== needle[j]) {
                j = pi[j - 1];
            }

            // 注意这里,如果不等与的话,则有 pi[i]=0,但是 pi 中每个项初始化就为0,于是省略了这一步
            if (needle[i] === needle[j]) {
                pi[i] = ++j;
            }
        }
        /**
         * 下面开始寻找 needle ,这个利用了前缀函数的性质,将 haystack 和 needle 想象成一个整体的字符串 totalStr:
         *      totalStr = needle + '$' + haystack;    // 注意'$'只是一个假想的字符,扮演分隔符的作用,在两个字符串中都不存在,作用是分隔 haystack 和 needle
         * 
         * 和上面求解 needle 的 pi 一样,设 totalStr 的前缀函数为 pi,j 为第 i 轮的上一轮的pi值,也就是 j=p[i-1]
         * 当 i<m 时,也就是等同于求解 needle 的 pi,这个已经在上面求解 needle 的 pi 的时候已经求出
         * 当 i=m 时,也就是 totalStr[i]='$' 的时候,必定有 pi[m]=0,因为分隔符在前面的字符中找不到,这也就是分隔符的作用,使得 j 重置为0,也避免真前缀、真后缀相互重叠的情况
         * 当 i>m 时,如果 pi[i] = m,这意味着什么呢?这意味着真前缀等于 needle,而真后缀就在 totalStr 的分隔符后半部分的字符串中,也就是在 haystack 找到了我们想要的字符串
         * 
         * 注意前缀函数的性质,当 i>m 时,我们并不需要保存totalStr[i] 的 pi值,因为 totalStr[i] 的求解只依赖两个参数:上一轮的 pi值 j、前缀函数 pi[0:j-1]
         * 而我们只需要找到pi[i]=m(i>m)的情况,也就是只需要前缀函数 pi[0:m-1],也就是 needle 的前缀函数
         **/

        // 此处,j 由于分隔符号的作用,已经重置为0。
        // 而遍历参数 i,如果以 totalStr 为基础,那么i初始化值为分隔符的下一位也就是 m+1,且 i<m+n+1
        // 但是此处的 i 以 haystack 为基础,分隔符的下一位就是 haystack 的第一位,也就是 0,所以 i=0,且 i<n
        for (let i = 0, j = 0; i < n; i++) {
            while (j > 0 && haystack[i] !== needle[j]) {
                j = pi[j - 1]
            }
            if (haystack[i] === needle[j]) {
                j++;
            }
            // j=m,意味着找到值为 needle 的真前缀(真后缀),计算其在 haystack 的位置直接返回
            if (j === m) {
                return i - j + 1
            }
        }
        return -1;
    };
posted @ 2022-06-08 18:39  Sebastian·S·Pan  阅读(20)  评论(0编辑  收藏  举报