跳转至

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

题目描述

给定一个字符串 s ,请计算这个字符串中有多少个回文子字符串。

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

 

示例 1:

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

示例 2:

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

 

提示:

  • 1 <= s.length <= 1000
  • s 由小写英文字母组成

 

注意:本题与主站 647 题相同:https://leetcode.cn/problems/palindromic-substrings/ 

解法

方法一:从中心向两侧扩展回文串

我们可以枚举回文串的中间点,然后向左右两边扩展,统计回文串的数量。注意,回文串可能包含奇数个字符,也可能不包含。因此这两种情况都要考虑。

时间复杂度 $O(n^2)$,其中 $n$ 是字符串 s 的长度。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Solution:
    def countSubstrings(self, s: str) -> int:
        def f(i, j):
            cnt = 0
            while i >= 0 and j < n:
                if s[i] != s[j]:
                    break
                cnt += 1
                i, j = i - 1, j + 1
            return cnt

        n = len(s)
        return sum(f(i, i) + f(i, i + 1) for i in range(n))
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
    private String s;

    public int countSubstrings(String s) {
        int ans = 0;
        this.s = s;
        for (int i = 0; i < s.length(); ++i) {
            ans += f(i, i);
            ans += f(i, i + 1);
        }
        return ans;
    }

    private int f(int i, int j) {
        int cnt = 0;
        for (; i >= 0 && j < s.length() && s.charAt(i) == s.charAt(j); --i, ++j) {
            ++cnt;
        }
        return cnt;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class Solution {
public:
    int countSubstrings(string s) {
        int ans = 0;
        int n = s.size();
        auto f = [&](int i, int j) -> int {
            int cnt = 0;
            for (; i >= 0 && j < n && s[i] == s[j]; --i, ++j) {
                ++cnt;
            }
            return cnt;
        };
        for (int i = 0; i < n; ++i) {
            ans += f(i, i) + f(i, i + 1);
        }
        return ans;
    }
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
func countSubstrings(s string) (ans int) {
    n := len(s)
    f := func(i, j int) (cnt int) {
        for ; i >= 0 && j < n && s[i] == s[j]; i, j = i-1, j+1 {
            cnt++
        }
        return
    }
    for i := range s {
        ans += f(i, i)
        ans += f(i, i+1)
    }
    return
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Solution {
    private var s: String = ""

    func countSubstrings(_ s: String) -> Int {
        var ans = 0
        self.s = s
        let length = s.count
        for i in 0..<length {
            ans += countPalindromes(i, i)
            ans += countPalindromes(i, i + 1)
        }
        return ans
    }

    private func countPalindromes(_ i: Int, _ j: Int) -> Int {
        var cnt = 0
        var i = i
        var j = j
        let chars = Array(s)

        while i >= 0 && j < chars.count && chars[i] == chars[j] {
            cnt += 1
            i -= 1
            j += 1
        }

        return cnt
    }
}

方法二:Manacher 算法

在 Manacher 算法的计算过程中,用 $p[i]-1$ 表示以第 $i$ 位为中心的最大回文长度,以第 $i$ 位为中心的回文串数量为 $\left \lceil \frac{p[i]-1}{2} \right \rceil$。

时间复杂度 $O(n)$,空间复杂度 $O(n)$。其中 $n$ 是字符串 s 的长度。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class Solution:
    def countSubstrings(self, s: str) -> int:
        t = '^#' + '#'.join(s) + '#$'
        n = len(t)
        p = [0 for _ in range(n)]
        pos, maxRight = 0, 0
        ans = 0
        for i in range(1, n - 1):
            p[i] = min(maxRight - i, p[2 * pos - i]) if maxRight > i else 1
            while t[i - p[i]] == t[i + p[i]]:
                p[i] += 1
            if i + p[i] > maxRight:
                maxRight = i + p[i]
                pos = i
            ans += p[i] // 2
        return ans
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
    public int countSubstrings(String s) {
        StringBuilder sb = new StringBuilder("^#");
        for (char ch : s.toCharArray()) {
            sb.append(ch).append('#');
        }
        String t = sb.append('$').toString();
        int n = t.length();
        int[] p = new int[n];
        int pos = 0, maxRight = 0;
        int ans = 0;
        for (int i = 1; i < n - 1; i++) {
            p[i] = maxRight > i ? Math.min(maxRight - i, p[2 * pos - i]) : 1;
            while (t.charAt(i - p[i]) == t.charAt(i + p[i])) {
                p[i]++;
            }
            if (i + p[i] > maxRight) {
                maxRight = i + p[i];
                pos = i;
            }
            ans += p[i] / 2;
        }
        return ans;
    }
}

评论