LeetCode5&647回文子串,Python带测试用例
LeetCode5.最长回文子串
一、题目
给你一个字符串 s,找到 s 中最长的回文子串。
二、思路
1.动态规划概要
动态规划法:把一个复杂问题分解为若干个相互重叠的子问题。
动态规划算法通常用于求解具有某种最优性质的问题。在这类问题中,可能会有许多可行解。每一个解都对应于一个值,我们希望找到具有最优值的解。动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算了很多次。如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间。我们可以用一个表来记录所有已解的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划法的基本思路。
2.解题思路
1.创建动态规划表dp (n x n的全为False的矩阵) ,用True和False来表示字符串s的每一个子串是否为回文。
2.双循环则进行遍历。
3.分情况讨论
(1) 边缘情况1: 子串长度为1, 则一定是回文
(2) 边缘情况2: 子串长度为2, 如果俩字符相同则是回文
(3) 非边缘情况则进行动态规划之状态转移: 如果b是回文, aba也一定是回文。
即判断是否为回文需同时满足两条件:
- 剥离左右最外层字符后的子字符串是回文
- 最外层的字符相同
三、题解
class Solution:
def longestPalindrome(self, s: str) -> str:
n = len(s)
# 特解,为空字符串和长度为的字符串时,直接返回该字符串
if n < 2:
return s
'''
创建动态规划表dp (n x n的全为False的矩阵) ,用True和False来表示字符串s的每一个子串是否为回文
s子串个数 = n + n-1 + ... + 1, e.g. 字符串babad有5 + 4 + 3 + 2 + 1 = 15种子串
start用来跟踪最长回文子串的起点, max_len代表最长回文子串的长度
'''
dp = [[False] * n for _ in range(n)] # dp为n个其中都为n个False组成的列表
"""
相当于:
dp = []
for i in range(n):
list = []
for j in range(n):
list.append(False)
dp.append(list)
"""
start, max_len = 0, 1
'''
如果字符串是"babad"
下面的双循环则进行以下遍历 (遍历所有可能的15个子字符串):
b
ba, a
bab, ab, b,
baba, abd, ba, a
babad, abad, bda, ad, d
dp[left][right]代表s被left和right指针相夹而得的子字符串
e.g. left = 0, right = 3, dp[left][right] = baba
'''
for right in range(n):
for left in range(0, right + 1):
'''
(0) 求子串跨越长度span
(1) 边缘情况1: 子串长度为1, 则一定是回文
(2) 边缘情况2: 子串长度为2, 如果俩字符相同则是回文
(3) 非边缘情况则进行动态规划之状态转移: 如果b是回文, aba也一定是回文
即判断是否为回文需同时满足两条件:
1. 剥离左右最外层字符后的子字符串是回文
2. 最外层的字符相同
'''
span = right - left + 1
if span == 1:
dp[left][right] = True
elif span == 2:
dp[left][right] = s[left] == s[right]
else:
dp[left][right] = dp[left + 1][right-1] and s[left] == s[right]
# 若新的回文出现, 判断是否需要更新最大长度
if dp[left][right]:
if span > max_len:
max_len = span
start = left
# 返回最长回文子串
return s[start:start + max_len]
l = Solution()
print(l.longestPalindrome("babad")) # bab
print(l.longestPalindrome("cbbd")) # bb
print(l.longestPalindrome("a")) # a
print(l.longestPalindrome("ac")) # a
print(l.longestPalindrome("aaaa")) # aaaa
时间复杂度:O(n^2)
其中 n 是字符串的长度。动态规划的状态总数为 O(n^2)
对于每个状态,我们需要转移的时间为O(1)。
空间复杂度:O(n^2),即存储动态规划状态需要的空间。
LeetCode647.Palindromic Substrings回文子串
一、题目
给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
二、题解
class Solution:
def countSubstrings(self, s: str) -> int:
n = len(s)
# 记录回文子串数量
ans = 0
'''
创建动态规划表dp (n x n的全为False的矩阵) ,用True和False来表示字符串s的每一个子串是否为回文
s子串个数 = n + n-1 + ... + 1, e.g. 字符串babad有5 + 4 + 3 + 2 + 1 = 15种子串
'''
dp = [[False] * n for _ in range(n)] # dp为n个其中都为n个False组成的列表
"""
相当于:
dp = []
for i in range(n):
list = []
for j in range(n):
list.append(False)
dp.append(list)
"""
'''
如果字符串是"babad"
下面的双循环则进行以下遍历 (遍历所有可能的15个子字符串):
b
ba, a
bab, ab, b,
baba, abd, ba, a
babad, abad, bda, ad, d
dp[left][right]代表s被left和right指针相夹而得的子字符串
e.g. left = 0, right = 3, dp[left][right] = baba
'''
for right in range(n):
for left in range(0, right + 1):
'''
(0) 求子串跨越长度span
(1) 边缘情况1: 子串长度为1, 则一定是回文
(2) 边缘情况2: 子串长度为2, 如果俩字符相同则是回文
(3) 非边缘情况则进行动态规划之状态转移: 如果b是回文, aba也一定是回文
即判断是否为回文需同时满足两条件:
1. 剥离左右最外层字符后的子字符串是回文
2. 最外层的字符相同
'''
span = right - left + 1
if span == 1:
dp[left][right] = True
elif span == 2:
dp[left][right] = s[left] == s[right]
else:
dp[left][right] = dp[left + 1][right - 1] and s[left] == s[right]
# 动态规划表为True,为回文子串,ans+1
if dp[left][right]:
ans += 1
# 返回回文子串数量
return ans
l = Solution()
print(l.countSubstrings("babad")) # 7
print(l.countSubstrings("abc")) # 3
print(l.countSubstrings("aaa")) # 6
print(l.countSubstrings("cbbd")) # 5
print(l.countSubstrings("a")) # 1
print(l.countSubstrings("ac")) # 2