剑指 Offer II 020. 回文子字符串的个数
给定一个字符串 s ,请计算这个字符串中有多少个回文子字符串。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
输入:s = "abc"
输出:3
解释:三个回文子串: "a", "b", "c"
输入:s = "aaa"
输出:6
解释:6个回文子串: "a", "a", "a", "aa", "aa", "aaa"
动态规划
对于一个子串而言,如果它是回文串,并且长度大于 2 , 那么将它首尾的两个字母去除之后,它仍 然是个回文串。例如对于字符串 “ababa”, 如果我们已经知道 “bab" 是回文串,那么 "ababa"一定 是回文串, 这是因为它的首尾两个字母都是 “a”。
根据这样的思路,我们就可以用动态规划的方法解决本题。
第 1 步:定义状态
我们用
d
P
(
i
,
j
)
dP(i, j)
dP(i,j)? 表示字符串
s
s
s? 的第
i
i
i? 到
j
j
j? 个字母组成的串 (下文表示成
s
[
i
:
j
]
)
s[i: j])
s[i:j])?? 是否为回文串:
d
P
(
i
,
j
)
=
{
?true,?
?如果子串?
S
i
…
S
j
?是回文串?
?false,?
?其它情况?
dP(i, j)= \begin{cases}\text { true, } & \text { 如果子串 } S_{i} \ldots S_{j} \text { 是回文串 } \\ \text { false, } & \text { 其它情况 }\end{cases}
dP(i,j)={?true,??false,???如果子串?Si?…Sj??是回文串??其它情况?? 这里的「其它情况」包含两种可能性:
-
s
[
i
,
j
]
s[i, j]
s[i,j] 本身不是一个回文串;
-
i
>
j
i>j
i>j, 此时
s
[
i
,
j
]
s[i, j]
s[i,j]? 本身不合法。
第 2 步:思考状态转移方程
整体上是两种,就是s[i] 与s[j] 相等,s[i] 与s[j] 不相等这两种。
那么我们就可以写出动态规划的状态转移方程:
d
P
(
i
,
j
)
=
d
P
(
i
+
1
,
j
?
1
)
?
a
n
d
?
(
S
i
=
=
S
j
)
dP(i, j)=dP(i+1, j-1)~and ~\left(S_{i}==S_{j}\right)
dP(i,j)=dP(i+1,j?1)?and?(Si?==Sj?) 也就是说,只有
s
[
i
+
1
:
j
?
1
]
s[i+1: j-1]
s[i+1:j?1] 是回文串,并且
s
s
s 的第
i
i
i 和
j
j
j 个字母相同时,
s
[
i
:
j
]
s[i: j]
s[i:j]? 才会是回文串。
第 3步:确定遍历顺序
首先从递推公式中可以看出,情况三是根据dp[i + 1][j - 1] 是否为true ,在对dp[i][j] 进行赋值true 的。
dp[i + 1][j - 1] 在 dp[i][j] 的左下角,如图:
举例,输入:“aaa”,dp[i][j] 状态如下:
图中有6个true,所以就是有6个回文子串。
- 时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
- 空间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
class Solution:
def countSubstrings(self, s: str) -> int:
n = len(s)
dp = [[False]* n for _ in range(n) ]
result = 0
for i in range(n-1,-1,-1):
for j in range(i,n):
if s[i] == s[j]:
if j - i <= 1:
result += 1
dp[i][j] = True
elif dp[i+1][j-1]:
result += 1
dp[i][j] = True
return result
双指针
动态规划的空间复杂度是偏高的,我们再看一下双指针法。
首先确定回文串,就是找中心然后想两边扩散看是不是对称的就可以了。
在遍历中心点的时候,要注意中心点有两种情况。
一个元素可以作为中心点,两个元素也可以作为中心点。
那么有人同学问了,三个元素还可以做中心点呢。其实三个元素就可以由一个元素左右添加元素得到,四个元素则可以由两个元素左右添加元素得到。
所以我们在计算的时候,要注意一个元素为中心点和两个元素为中心点的情况。
这两种情况可以放在一起计算,但分别计算思路更清晰,我倾向于分别计算,代码如下:
class Solution:
def countSubstrings(self, s: str) -> int:
result = 0
n = len(s)
for i in range(n):
result += self.extend(s, i, i, n)
result += self.extend(s, i, i+1, n)
return result
def extend(self, s, i, j, n):
res = 0
while i >= 0 and j < n and s[i] == s[j]:
i -= 1
j += 1
res += 1
return res
|