题目
给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
示例 1:
输入:"abc"
输出:3
解释:三个回文子串: "a", "b", "c"
示例 2:
输入:"aaa"
输出:6
解释:6个回文子串: "a", "a", "a", "aa", "aa", "aaa"
提示:
- 输入的字符串长度不会超过 1000 。
来源:力扣(LeetCode)
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
重点理解
- 统计回文子串总数;
- 回文子串具体到不同开始位置或结束位置,即使相同字符也视作不同的回文字串
测试用例
- abc
- aaa
- aba
解题思路
- 中心扩展法
- 根据回文字符串的特点,以每一个元素为中心向两边扩展,左右两边同时移动,直到有一边到达起点或终点停止。
- 这个过程中出现以该元素为中心的左右两边字符相同并且移动不间断,就可以认为是一条回文子串。
- 回文子串既有奇数长度,也有偶数长度,以不同情况分别讨论。奇数长度情况时:两指针初始指向同一位置;偶数长度情况时:左右两指针指向元素位置相邻。
- 动态规划
- 利用 dp[ i ][ j ] 二维动态数组,i & j 可以不断变化,这里可以指代 left & right 指针值,实质也是中心扩展;
- 首先初始化dp数组,全部定义为False;
- 当 i = j 时,即两指针指代同一元素,表现为动态数组中的对角线,根据题目要求,不同位置的单个字符也为回文子串,所以对角线全部赋值为True;
- 这里为考虑所有情况,对字符串进行两层循环遍历,外层指代字符串的每一个元素位置,内层代表截止到该元素前的所有元素位置遍历,不断缩小区间确定是否为该长度的回文子串。
提交代码
1.中心扩展法
class Solution:
def countSubstrings(self, s: str) -> int:
count = 0
l = len(s)
# 偶数长度的回文子串情况
for left in range(l-1):
right = left + 1
while left >= 0 and right < l and s[left] == s[right]:
count += 1
left -= 1
right += 1
# 奇数长度的回文子串情况
for i in range(l):
right = left = i
while left >= 0 and right < l and s[left] == s[right]:
count += 1
left -= 1
right += 1
return count
2.动态规划
class Solution:
def countSubstrings(self, s: str) -> int:
l = len(s)
count = 0
if l == 0 or s is None:
return 0
# 定义和初始化dp数组
dp = [[False for _ in range(l)] for _ in range(l)]
# 对角线赋值为True,相当于把字符串元素映射在二维数组的对角线上
for i in range(l):
dp[i][i] = True
# 遍历字符串,更新dp数组,控制一个指针位置不变,依次移动另一指针位置,看两指针间元素是否为回文子串
for j in range(l):
for i in range(0, j):
# 单个字符
if j - i == 0:
dp[i][j] = True
# 相邻字符
elif j - i == 1 and s[i] == s[j]:
dp[i][j] = True
# 相隔多个字符
elif j - i > 1 and s[i] == s[j]:
dp[i][j] = dp[i + 1][j - 1]
# 遍历dp数组,数True的个数
for i in range(l):
for j in range(i, l):
if dp[i][j] is True:
count += 1
return count
总结
- for 循环和while循环组合,起初尝试在同一种循环里进行,但无法兼顾奇数长度和偶数长度情况,需要分开实现,回文子串结果与原字符串长度无关;
- 双指针方法比较常用,再配合具体的题目场景设置,双指针既可以作用相当,也可以有快慢之分,如在环中的应用,通过相遇来评定是否存在环;
- 动态规划的方法运用还不熟练,在涉及可以用到双指针的题目上,再多尝试动态数组来实现,一方面动态数组元素可以不严格为具体值,只需要保存“是/否”概念的结果即可,另一方面能够包含较多情况便于分类。