代码随想录算法训练营第九天 | 28. 找出字符串中第一个匹配项的下标、459. 重复的子字符串

104 阅读2分钟

KMP算法

前缀: 不包含最后一个字符的所有以第一个字符开头的连续子串(aabaaf: a,aa,aab,aaba,aabaa)
后缀: 不包含第一个字符的所有以最后一个字符结尾的连续子串(aabaaf: f,af,aaf,baaf,abaaf)
最长相等前后缀: 前缀子串和后缀子串相等时最大的子串长度(a 0, aa 1, aab 0, aaba 1, aabaa 2, aabaaf 0)
前缀表/next数组: [0,1,0,1,2,0]
求next数组: i的含义是前缀子串的末尾,j的含义是后缀子串的开头和公共子串的长度,卡哥视频:理论代码

function getNext(s: string): number[] {
  let j = 0
  const next: number[] = [0]
  for (let i = 1; i < s.length; i++) {
    while (j > 0 && s[i] !== s[j]) {
      j = next[j - 1]
    }
    if (s[i] === s[j]) {
      j++
    }
    next[i] = j
  }
  return next
}

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

先求出前缀表,然后用i指向haystack,j指向needle,j代表的就是公共子串末尾的位置

function strStr(haystack: string, needle: string): number {
  const next = getNext(needle)
  let j = 0
  for (let i = 0; i < haystack.length; i++) {
    while (j > 0 && haystack[i] !== needle[j]) {
      j = next[j - 1]
    }
    if (haystack[i] === needle[j]) {
      j++
    }
    if (j === needle.length) {
      return i - j + 1
    }
  }
  return -1
};
function getNext(s: string): number[] {
  let j = 0
  const next: number[] = [0]
  for (let i = 1; i < s.length; i++) {
    while (j > 0 && s[i] !== s[j]) {
      j = next[j - 1]
    }
    if (s[i] === s[j]) {
      j++
    }
    next[i] = j
  }
  return next
}

459. 重复的子字符串

如果字符串由某个子串重复构成,那么其最长相等前后缀长度一定是字符串的长度减去一个子串的长度,比如abcabcabc,它的最长相等前后缀是abcabc,其长度是abcabcabc.length-abc.length,同样,如果知道最长相等前后缀长度,那么子串的长度就是字符串长度减去最长相等前后缀长度,即abcabcabc.length-abcabc.length,而求最长相等前后缀长度,可以直接求前缀表,前缀表最后一个元素就是最长相等前后缀长度,所以满足最长相等前后缀长度大于0,且字符串长度是子串长度的整数倍就可以了

function repeatedSubstringPattern(s: string): boolean {
  const next = getNext(s)
  const len = next[s.length - 1] // 最长公共前后缀长度
  return len && s.length % (s.length - len) === 0
};
function getNext(s: string): number[] {
  let j = 0
  const next: number[] = [0]
  for (let i = 1; i < s.length; i++) {
    while (j > 0 && s[i] !== s[j]) {
      j = next[j - 1]
    }
    if (s[i] === s[j]) {
      j++
    }
    next[i] = j
  }
  return next
}