[路飞]_leetcode刷题_647. 回文子串

102 阅读2分钟

「这是我参与2022首次更文挑战的第25天,活动详情查看:2022首次更文挑战

题目

647. 回文子串

给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。

回文字符串 是正着读和倒过来读一样的字符串。

子字符串 是字符串中的由连续字符组成的一个序列。

具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。

示例 1:

输入:s = "abc"
输出:3
解释:三个回文子串: "a", "b", "c"

示例 2:

输入:s = "aaa"
输出:6
解释:6个回文子串: "a", "a", "a", "aa", "aa", "aaa"

解法一

暴力解。

思路

这题其实暴力解的思路其实也很好想,就是遍历求出每一个子串,并校验该子串是不是回文子串,但是这个时间复杂度就特别高了,遍历所有的子串需要O(n2),再校验该子串是否回文又需要O(n),两个操作是串行的,所以总的复杂度为O(n3)。这个代码其实很好写,我们就不写了,估计也是过不了的。

解法一

中心拓展法。

思路

其实暴力的缺点就是,很多不是回文串的,我们也去遍历并校验了一遍,浪费了很多时间。我们如果能优化掉一些非回文子串的枚举情况,那么就可以节省很多时间。

中心拓展法呢,我们先找到一个中心,然后左右指针分别往左和右走,如果相等,则说明是子串,子串数+1,然后左右指针继续走。然后把所有可能的中心都这样遍历操作一遍即可。

那么问题就变成了,如果求出所有的可能的回文中心。

回文中心点有两种可能,假设字符串长度为n

  1. 如果是奇数回文串,中心点就是一个字符,那么字符串的任何一个字符都有可能是回文中心,即有n个。
  2. 如果是偶数回文串,中心点就是两个字符,那么字符串的任何相邻的两个字符都有可能是回文中心,即有n-1个。

故总共的可能的回文中心为2n-1个,我们只需要遍历这2n-1个回文中心,从中心出发去拓展回文串,即可算法总共的回文串的数量。

中心拓展回文串的满足条件是

  1. 左指针 >= 0
  2. 右指针 < n
  3. 左指针对应的字符和右指针对应的字符相等。

我们可以先遍历奇数中心点,再遍历偶数中心点,也是可以的,也好理解。

但是其实两者是可以同时遍历的,主要要明白从中心点开始拓展的时候,其实的左右指针的下标如何计算。 左右指针和下标其实是有映射关系的:

  1. 字符串末节点下标为0时,字符串的回文串中心点,左右下标分别为[0,0]

  2. 字符串末节点下标为1时,字符串的回文串中心点,左右下标分别为[0,1]

  3. 字符串末节点下标为2时,字符串的回文串中心点,左右下标分别为[1,1]

  4. 字符串末节点下标为3时,字符串的回文串中心点,左右下标分别为[1,2]

  5. 字符串末节点下标为4时,字符串的回文串中心点,左右下标分别为[2,2]

    ...

  6. 字符串末节点下标为i时,字符串的回文串中心点,左右下标分别为[Math.floor(i/2),Math.floor(i/2) + i%2]

所以下标和当前下标对应的回文中心点的左右指针下标关系找到了,长度为n的字符串的回文中心点的总数为2n-1。

那么就可以开始编码了

代码如下

/**
 * @param {string} s
 * @return {number}
 */
var countSubstrings = function(s) {
    let len = s.length;
    let result = 0;
    // 遍历所有的可能的回文中心点
    for(let i=0;i<len*2-1;i++){
        let left = Math.floor(i/2);
        let right = left + i%2;
        while(left>=0&& right<len&& s[left] == s[right]){
            left--;
            right++;
            result++;
        }
    }
    return result;
};

复杂度分析

时间复杂度:O(n2),遍历所有中心点为n,拓展回文串又是n,两者串行,所以为O(n2)

空间复杂度:O(1),没有额外申请空间存储数据。