剑指 Offer II 020. 回文子字符串的个数(647. 回文子串)

题目:

 

思路:

【1】动态规划的方式

动态规划的原理,如ababa为例
二维数组的情况大致是这样:
[   a   ,   b  , a  ,   b  ,   a
a:[true, false, true, false, true], 
b:[false, true, false, true, false], 
a:[false, false, true, false, true], 
b:[false, false, false, true, false], 
a:[false, false, false, false, true]
]
其实可以看做是双指针的另类思想的体现
如纵坐标代表的是左指针,横坐标代表的是右指针
那么这块
    a   ,   b  , a  ,   b  ,   a
a:[true, false, true, false, true], 
代表的是左指针不动,右指针不断往右移动,是否是回文字符串的判断

 

【2】中心扩展法(实质的思路和动态规划的思路类似)

核心问题理解:
为什么有 2 * len - 1 个中心点?
    aba 有5个中心点,分别是 a、b、c、ab、ba
    abba 有7个中心点,分别是 a、b、b、a、ab、bb、ba

什么是中心点?
    中心点即 left 指针和 right 指针初始化指向的地方,可能是一个也可能是两个

为什么不可能是三个或者更多?
    因为 3 个可以由 1 个扩展一次得到,4 个可以由两个扩展一次得到

 

【3】Manacher 算法

代码展示:

Manacher 算法的方式(要着重理解思路):

//时间3 ms击败86.24%
//内存39.7 MB击败56.1%
//时间复杂度:O(n)。即 Manacher 算法的时间复杂度,由于最大回文右端点 rm 只会增加而不会减少,故中心拓展进行的次数最多为 O(n),此外我们只会遍历字符串一次,故总复杂度为 O(n)。
//空间复杂度:O(n)。
class Solution {
    public int countSubstrings(String s) {
        int n = s.length();
        StringBuffer t = new StringBuffer("$#");
        for (int i = 0; i < n; ++i) {
            t.append(s.charAt(i));
            t.append('#');
        }
        n = t.length();
        t.append('!');

        int[] f = new int[n];
        int iMax = 0, rMax = 0, ans = 0;
        for (int i = 1; i < n; ++i) {
            // 初始化 f[i]
            f[i] = i <= rMax ? Math.min(rMax - i + 1, f[2 * iMax - i]) : 1;
            // 中心拓展
            while (t.charAt(i + f[i]) == t.charAt(i - f[i])) {
                ++f[i];
            }
            // 动态维护 iMax 和 rMax
            if (i + f[i] - 1 > rMax) {
                iMax = i;
                rMax = i + f[i] - 1;
            }
            // 统计答案, 当前贡献为 (f[i] - 1) / 2 上取整
            ans += f[i] / 2;
        }

        return ans;
    }

}

 

中心扩展的方式:

//时间3 ms击败86.24%
//内存39.7 MB击败59.99%
//时间复杂度是 O(N^2)
//空间复杂度是 O(1)
class Solution {
    public int countSubstrings(String s) {
        // 中心扩展法
        int ans = 0;

        for (int center = 0; center < 2 * s.length() - 1; center++) {
            // left和right指针和中心点的关系是?
            // 首先是left,有一个很明显的2倍关系的存在,其次是right,可能和left指向同一个(偶数时),也可能往后移动一个(奇数)
            // 大致的关系出来了,可以选择带两个特殊例子进去看看是否满足。
            int left = center / 2;
            int right = left + center % 2;

            while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
                ans++;
                left--;
                right++;
            }
        }

        return ans;
    }
}

//中心扩展法(奇偶分开的方式)
//时间1 ms击败100%
//内存39.7 MB击败52.23%
class Solution {
    public int countSubstrings(String s) {
        char[] cs = s.toCharArray();
        int n = cs.length;

        int ans = 1;
        for (int i = 0; i < n - 1; i++)
            ans += centerSpread(cs, i, i) + centerSpread(cs, i, i + 1);

        return ans;
    }

    private int centerSpread(char[] cs, int l, int r) {
        while (0 <= l && r < cs.length && cs[l] == cs[r]) {
            l--;
            r++;
        }
        return (r - l) / 2;
    }
}

 

动态规划的方式:

//时间9 ms击败38.13%
//内存41.8 MB击败17.32%
//时间复杂度为 O(N^2)
//空间复杂度为 O(N^2)
class Solution {
    public int countSubstrings(String s) {
        // 动态规划法
        boolean[][] dp = new boolean[s.length()][s.length()];
        int ans = 0;

        // 其实这个双循环相当于对字符串进行分割(进行了全分割)
        // 如ababa
        // 第一次被分为 a
        // 第二次被分为 ab 和 b
        // 第三次被分为 aba 和 ba 和 a
        // 第四次被分为 abab 和 bab 和 ab 和 b
        // 第五次被分为 ababa 和 baba 和 aba 和 ba 和 a
        for (int j = 0; j < s.length(); j++) {
            for (int i = 0; i <= j; i++) {
                // 这里将条件拆分一下
                // 1)s.charAt(i) == s.charAt(j) && (j - i < 2) // 两字符相等,且是同一字符,必然是回文字符串
                // 2)s.charAt(i) == s.charAt(j) && (j - i >= 2 && dp[i + 1][j - 1]) 
          // 两字符相等,但长度大于1,即ababa这种,那么就要判断左右各减去一个字符的情况,即bab这部分 // 由于之前已经做了判断所以直接查找即可 if (s.charAt(i) == s.charAt(j) && (j - i < 2 || dp[i + 1][j - 1])) { dp[i][j] = true; ans++; } } } return ans; } }

 

中心扩展法
posted @ 2023-03-09 12:35  忧愁的chafry  阅读(21)  评论(0编辑  收藏  举报