「前端刷题」214.最短回文串(HARD)

106 阅读1分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第13天,点击查看活动详情

题目(Shortest Palindrome)

链接:https://leetcode-cn.com/problems/shortest-palindrome
解决数:398
通过率:38.6%
标签:字符串 字符串匹配 哈希函数 滚动哈希 
相关公司:microsoft netease bloomberg 

给定一个字符串 s,你可以通过在字符串前面添加字符将其转换为回文串。找到并返回可以用这种方式转换的最短回文串。

 

示例 1:

输入: s = "aacecaaa"
输出: "aaacecaaa"

示例 2:

输入: s = "abcd"
输出: "dcbabcd"

 

提示:

  • 0 <= s.length <= 5 * 104
  • s 仅由小写英文字母组成

思路

从暴力法可以看出,其实就是求 s 的「最长回文前缀」,然后在 rev_s 的后缀中砍掉这个回文,再加到 s 前面。

这个最长前缀是回文的,它翻转之后等于它自己,出现在 rev_s 的后缀,这不就是公共前后缀吗?KMP 的 next 数组记录的就是一个字符串的每个位置上,最长公共前后缀的长度。公共前后缀指的是前后缀相同。

因此,我们 “制造” 出公共前后缀,去套 KMP。

s:abab,则 s + '#' + rev_s,得到 str :abab#baba

求出 next 数组,最后一项就是 str 的最长公共前后缀的长度,即 s 的最长回文前缀的长度。

如果不加 #,'aaa'+'aaa'得到'aaaaaa',求出的最长公共前后缀是 6,但其实想要的是 3。

代码

const shortestPalindrome = (s) => {
  const rev_s = s.split('').reverse().join('');
  const str = s + "#" + rev_s;
  const next = new Array(str.length).fill(0);
  // 抽出来,方便学习记忆,这是我写的模板
  const kmp = (next, str) => {
    next[0] = 0;
    let len = 0;
    let i = 1;
    while (i < str.length) {
      if (str[i] == str[len]) {
        len++;
        next[i] = len;
        i++;
      } else {
        if (len == 0) {
          next[i] = 0;
          i++;
        } else {
          len = next[len - 1];
        }
      }
    }
  };
  kmp(next, str);
  const maxLen = next[str.length - 1]; // 最长回文前缀的长度
  const add = s.substring(maxLen).split('').reverse().join('');
  return add + s;
};
const shortestPalindrome = (s) => { // s:ananab
  const len = s.length;
  const rev_s = s.split('').reverse().join(''); // rev_s:banana
  for (let i = len; i >= 0; i--) {              // ananab==banana?、anana==anana?、……
    if (s.substring(0, i) == rev_s.substring(len - i)) {
      return rev_s.substring(0, len - i) + s;   // 返回 b + ananab
    }
  }
}

也可以判断 s 的前缀部分是否回文(前缀部分尽量长),但循环套循环的,代码超时了:

120 / 120 test cases passed, but took too long.

const isPalindrome = (s) => {
  let i = 0, j = s.length - 1;
  while (i < j) {
    if (s[i] !== s[j]) return false;
    i++;
    j--;
  }
  return true;
};
const shortestPalindrome = (s) => {
  const len = s.length;
  for (let i = len; i >= 0; i--) {
    const prefix = s.substring(0, i); // 前缀部分,从大的开始考察
    if (isPalindrome(prefix)) {       // 一旦是回文就准备return结果
      const add = s.substring(i).split('').reverse().join('');
      return add + s;
    }
  }
};