【区间DP、双指针】647. Palindromic Subst
题目描述:
Given a string, your task is to count how many palindromic substrings in this string.
The substrings with different start indexes or end indexes are counted as different substrings even they consist of same characters.
Example 1:
Input: "abc"
Output: 3
Explanation: Three palindromic strings: "a", "b", "c".
Example 2:
Input: "aaa"
Output: 6
Explanation: Six palindromic strings: "a", "a", "a", "aa", "aa", "aaa".
Note:
- The input string length won't exceed 1000.
解题思路:
该题计算字符串中有多少个不同的回文子串,注意:具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被计为是不同的子串。
首先想到暴力,时间复杂度 O(N^3),pass。
方法1(双指针):
回文串按照长度分为两种:长度为奇数的的回文串(如 'a', 'aba')和长度为偶数的回文串(如 'aa', 'abba')。双指针的做法利用了这个性质,将字符串的每个位置 i 分为两种情况:
- 第一种是长度为 2 的子串(i, i+1)开始,如果这两字符相等,则向两边拓展,判断长度为 4、6... 是不是回文串;
- 第二种是长度为 3 的子串 (i, i+2) 开始,如果第一个和第三个字符相等,则向两边拓展,判断长度为 5、7... 是不是回文串。
对于每个位置 i,累加两种情况下的回文子串个数,最后,再加上字符串的长度就是答案(因为单个字符也是回文串)。
举例:
如 s = "aaaaa", 假设现在遍历到 s[1] = 'a',分两种情况:
- 取 [1,2] 构成 'aa',s[1] == s[2],是回文串,则向两端移动;s[0] == s[3],是回文串,则向两端移动;越界,则 s[1] 位置的偶数长度的子串为 2;
- 取 [1,3] 构成 'aaa',s[1] == s[3],是回文串,则向两端移动;s[0] == s[4],是回文串,则向两端移动;越界,则 s[1] 位置的奇数长度的子串为 2。
每个位置都从长度为 2 和长度为 3 的子串分别计算,向两端扩展,计算偶回文串和奇回文串的个数。
时间复杂度 O(N^2),空间复杂度 O(1)。
Python3 实现:
class Solution:
# 方法1:双指针,分奇回文串和偶回文串
def countSubstrings(self, s: str) -> int:
lens = len(s)
even = odd = 0
for i in range(lens):
even += self.count(s, lens, i, i+1) # 统计偶回文串个数
odd += self.count(s, lens, i, i+2) # 统计奇回文串个数
return lens + even + odd # 单个字符也是回文串
def count(self, s, lens, i, j):
cnt = 0
while i >= 0 and j < lens and s[i] == s[j]:
cnt += 1
i -= 1
j += 1
return cnt
print(Solution().countSubstrings("aaa")) # 6
print(Solution().countSubstrings("aabba")) # 8
方法2(动态规划):
动态规划的思想是,用 dp[i][j]
表示子串 s[i,j]
是不是回文串,初始化为 False。最后,对 dp[lens(s)][len(s)] 所有的 True 求和,就是最后的答案。
接下来找状态转移方程:
当我们要确定 dp[i][j] 是否为 True,需要考虑:(1)dp[i+1][j-1]
是回文串吗?;(2)s[i] == s[j]
吗?
因此,我们得到转移方程:dp[i][j] = True, if dp[i+1][j-1] == True and s[i] == s[j]
举例:
如 s = "abcba", dp[0][4] = True,因为 dp[0+1][4-1] 为 True 且 s[0] == s[4]。
编程时注意点:
1、在进行双循环遍历时,应该按照 (0,0);(1,1)、(0,1);(2,2)、(1,2)、(0,2);(3,3)、(2,3)、(1,3)、(0,3) 的顺序遍历,这样可以保证在计算 dp[i][j] 时 dp[i+1][j-1] 已经计算出来了。
2、对于 s = 'aa' 这种,计算 dp[0][1] 为 True 时,由于 dp[0+1][1-1] 导致 dp[1][0] 无意义,因此对于这种情况,只需要保证 s[0] == s[1] 即可。
Python3 实现:
class Solution:
# 方法2:dp[i][j] = True, if dp[i+1][j-1] == True and s[i] == s[j]
def countSubstrings(self, s: str) -> int:
lens = len(s)
dp = [[False] * lens for _ in range(lens)]
for j in range(lens): # 注意遍历的顺序
dp[j][j] = True
for i in range(j-1, -1, -1): # 注意遍历的顺序
if i+1 <= j-1 and dp[i+1][j-1] == True and s[i] == s[j]:
dp[i][j] = True
elif i+1 > j-1 and s[i] == s[j]:
dp[i][j] = True
cnt = 0
for i in range(lens):
cnt += sum(dp[i])
return cnt
print(Solution().countSubstrings("aaaa")) # 10
print(Solution().countSubstrings("aabbaa")) # 11