LeetCode日记——最长回文子串

162 阅读2分钟

5.最长回文子串

从定义分析回文子串特征

“回文”可以理解为从中间向两侧对称地扩散,例如 “abcdcba”

这个“中间”,可以是单个字符,也可以是两个相临的、且相等的字符,例如,以单个字符为中心: “abcdcba” ,这个就是以单个字符“d”为中心;以两个相邻相等的字符为中心: “abcddcba” ,这个就是以“dd”为中心。

  • 如果字符串为空,那结果肯定为空字符串;
  • 如果字符串长度为 1,那结果肯定为该字符串本身;

定义一个变量,设最长回文子串为:

let res = '';

遍历字符串 s

遍历字符串时,根据当前遍历到的字符s[i],分为两种情况:

  1. 中心为单个字符,即以该字符s[i]为中心,判断从该字符能从两侧扩散出多长的回文子串;
  2. 中心为两个相等字符,即当且仅当s[i] === s[i + 1]时,当前可以作为回文子串的中心;

中心为单个字符

根据遍历到的i,设置三个变量:

// 左侧字符位置
let currLeft = i - 1;
// 右侧字符位置
let currRight = i + 1;
// 当前的回文子串
let currStr = '';

有了左右各自的字符位置,定义一个while循环,在左侧位置和右侧位置均有效的时候进入循环。

在循环中,判断以s[i]为中心,两侧的字符是否相等,如果相等,说明得到了一个回文子串,判断该回文子串和之前的回文子串的长度,如果更长,就缓存下来,PS:一旦遇到两侧字符不相等的情况,结束当前的 while 循环:

while (currLeft >= 0 && currRight < s.length) {
  // 在两侧范围内判断回文
  if (s[currLeft] === s[currRight]) {
    // 是回文,将当前回文保存下来
    currStr = s.slice(currLeft, currRight + 1);
    currLeft--;
    currRight++;
  } else {
    break;
  }
}
​
if (currStr.length > res.length) {
  // 如果当前的回文子串更长,那就缓存下来
  res = currStr;
}

中心为两个相等的字符

注意:此时,一旦有两个相邻且相等的字符,那这两个字符就是回文子串,需要判断之前得到的回文子串的长度,如果之前的长度就小于 2,那么此时这两个相邻且相等的回文子串本就该缓存下来:

if (s[i] === s[i + 1]) {
  if (res.length < 2) {
    res = s.slice(i, i + 2);
  }
}

同上,根据遍历的i,设置三个变量:

let _currLeft = i - 1;
// 中心长度为2,所以右侧的下标为当前遍历值i加上2!
let _currRight = i + 2;
let _currStr = '';
// 进入while循环的逻辑相同,即下标在字符串s中有效
while (_currLeft >= 0 && _currRight < s.length) {
  if (s[_currLeft] === s[_currRight]) {
    _currStr = s.slice(_currLeft, _currRight + 1);
    _currLeft--;
    _currRight++;
  } else {
    break;
  }
}
if (_currStr.length > res.length) {
  // 保存更长的子串
  res = _currStr;
}

最终代码

function longestPalindrome(s: string): string {
  if (s.length === 0) return '';
  if (s.length === 1) return s;
  // 用一个指针来指向回文子串的中心
  // 既然是回文,那就会向向当前中心的两侧均匀扩散
  let res = s[0];
  // 先判断中心长度为1的情况
  for (let i = 0; i < s.length; i++) {
    /** -------------------分割线:以下代码用于判断回文子串的中心为单个字符的情况------------- */
    // 先判断中心的长度为1
    let currLeft = i - 1;
    let currRight = i + 1;
    let currStr = '';
    while (currLeft >= 0 && currRight < s.length) {
      // 在两侧范围内判断回文
      if (s[currLeft] === s[currRight]) {
        // 是回文,将当前回文保存下来
        currStr = s.slice(currLeft, currRight + 1);
        currLeft--;
        currRight++;
      } else {
        break;
      }
    }
    if (currStr.length > res.length) {
      res = currStr;
    }
​
    /** -------------------分割线:以上代码用于判断回文子串的中心为单个字符的情况----------------- */
​
    /** -------------------分割线:以下代码用于判断回文子串的中心为两个字符的情况----------------- */
    //"abcdefghhgfedcba"\n
    if (s[i] === s[i + 1]) {
      if (res.length < 2) {
        // 遇到俩相邻的字符是相等的,并且之前的结果的长度小于2,那么这俩相邻的可以视为回文子串
        res = s.slice(i, i + 2);
      }
​
      let _currLeft = i - 1;
      let _currRight = i + 2;
      let _currStr = '';
      while (_currLeft >= 0 && _currRight < s.length) {
        if (s[_currLeft] === s[_currRight]) {
          _currStr = s.slice(_currLeft, _currRight + 1);
          _currLeft--;
          _currRight++;
        } else {
          break;
        }
      }
      if (_currStr.length > res.length) {
        res = _currStr;
      }
    }
​
    /** -------------------分割线:以上代码用于判断回文子串的中心为两个字符的情况---------------- */
  }
​
  return res;
}