算法练习day8

93 阅读2分钟

KMP

kmp算法只要用在字符串匹配上,当字符串不匹配时,可以知道一部分之前已经匹配的文本内容,避免从头再去匹配

如何记录已经匹配的文本内容,就是kmp算法的重点

前缀表

前缀:不包含最后一个字符的以第一个字符开头的连续子串

后缀:不包含第一个字符的以最后一个字符结尾的连续子串

最长相等前后缀:相同前后缀长度

为什么用前缀表

字符串匹配失败的位置是后缀子串的后一位,那么找到与其相同的前缀的后一位重新匹配就可以了

如何计算前缀表 就是计算next数组

方法一:前缀表统一减一,然后位置统一右移一位,比较s[i]和s[j+1],如果不相等,则j = next[j]

方法二:前缀表统一不减一,比较s[i]和s[j],如果不相等,则j = next[j-1]

一、找出字符串中第一个匹配项的下标

/**
 * next数组减1
 * @param {string} haystack
 * @param {string} needle
 * @return {number}
 */
var strStr = function (haystack, needle) {
    if(!needle.length) {
        return 0
    }
    let size = haystack.length
    let next = getNext(needle)
    let j = -1
    for(let i = 0; i < size; i++) {
        while( j >= 0 && haystack[i] !== needle[j+1]) {
            j = next[j]
        }
        if(needle[j+1] === haystack[i]) {
            j++
        }
        if(j === needle.length -1 ) {
            return (i - needle.length + 1)
        }
    }
    return -1
};

function getNext(needle) {
    let size = needle.length
    let next = new Array(size).fill(-1)
    let left = -1
    let right = 1
    while (right < size) {
        if (left >= 0 && needle[left+1] !== needle[right]) {
            left = next[left]
        }
        if (needle[left+1] === needle[right]) {
            left++
        }
        next[right] = left
        right++
    }
    return next
}
/**
 * next数组不减1
 * @param {string} haystack
 * @param {string} needle
 * @return {number}
 */
var strStr = function (haystack, needle) {
    if(!needle.length) {
        return 0
    }
    let size = haystack.length
    let next = getNext(needle)
    let j = 0
    for(let i = 0; i < size; i++) {
        while( j > 0 && haystack[i] !== needle[j]) {
            j = next[j - 1]
        }
        if(needle[j] === haystack[i]) {
            j++
        }
        if(j === needle.length) {
            return (i - needle.length + 1)
        }
    }
    return -1
};

function getNext(needle) {
    let size = needle.length
    let next = new Array(size).fill(0)
    let left = 0
    let right = 1
    while (right < size) {
        if (left > 0 && needle[left] !== needle[right]) {
            left = next[left - 1]
        }
        if (needle[left] === needle[right]) {
            left++
        }
        next[right] = left
        right++
    }
    return next
}

二、重复的子字符串

问题要点

思路,最小重复子串其实就是 字符串去除最长相等前后缀,可以数学证明,那关键就在于通过kmp找到字符串的前缀表,判断字符串的长度可以最小重复子串整除

/**
 * @param {string} s
 * @return {boolean}
 */
var repeatedSubstringPattern = function(s) {
    let size = s.length
    function getNext(s) {
        let left = 0
        let right = 1
        let next = new Array(size).fill(0)
        while(right < size) {
            while(left > 0 && s[left] !== s[right]) {
                left = next[left - 1]
            }
            if(s[left] === s[right]) {
                left++
            }
            next[right] = left
            right++
        }
        return next
    }
    if(!size) {
        return false
    }
    let next = getNext(s)
    if(next[size - 1] !== 0 && size % (size - next[size - 1]) === 0) {
        return true
    }
    return false
};