文章目录
最长公共子序列
给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。
若这两个字符串没有公共子序列,则返回 0。
注意区分:子序列和子串的区别。子序列的话只要元素都存在,并且顺序一致就可以。
动态规划的思路:
第一步:明确 dp 数组的含义。
对于字符串 str1
和 str2
,一般都需要构造这样的一个 table。
dp[i][j]
的含义是:对于 str1[1..i]
和 str2[1...j]
, 他们的 LCS 长度是 dp[i][j]
.
第二步:定义 base case。
我们专门让索引为 0 的行和列表示空串,dp[0][…] 和 dp[…][0] 都应该初始化为 0,这就是 base case。
比如说,按照刚才 dp 数组的定义,dp[0][3]=0 的含义是:对于字符串 “” 和 “bab”,其 LCS 的长度为 0。因为有一个字符串是空串,它们的最长公共子序列的长度显然应该是 0。
第三步:定义状态转移方程
如果某个字符应该在 lcs 中,那么这个字符肯定同时存在于 s1 和 s2 中,因为 lcs 是最长公共子序列嘛。所以本题的思路是这样:
用两个指针 i 和 j 从后往前遍历 s1 和 s2,如果 s1[i]==s2[j],那么这个字符一定在 lcs 中;否则的话,s1[i] 和 s2[j] 这两个字符至少有一个不在 lcs 中,需要丢弃一个。
Python 代码是这样的:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
# 动态规划的解法
m, n = len(text1), len(text2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(1, m + 1):
for j in range(1, n + 1):
if text1[i-1] == text2[j-1]: # dp 第一行和第一列是空下的0情况
# 找到一个lcs中的字符
dp[i][j] = 1 + dp[i-1][j-1]
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
return dp[-1][-1]
最长回文子序列
给定一个字符串 s ,找到其中最长的回文子序列,并返回该序列的长度。可以假设 s 的最大长度为 1000 。
示例 1:
输入:
"bbbab"
输出:
4
一个可能的最长回文子序列为 “bbbb”。
用动态规划来做。
数组 dp[i][j]
表示 s[i:j]
最长回文子序列的长度。
对角线位置的动态数组的值为 1.
class Solution:
def longestPalindromeSubseq(self, s: str) -> int:
n = len(s)
dp = [[0]*n for _ in range(n)]
for j in range(n-1,-1,-1):
dp[j][j] = 1
for i in range(j+1,n):
if s[j] == s[i]:
dp[j][i] = 2 + dp[j+1][i-1]
else:
dp[j][i] = max(dp[j+1][i],dp[j][i-1])
return dp[0][n-1]
统计全为1的正方形子矩阵
示例:
输入:matrix =
[
[0,1,1,1],
[1,1,1,1],
[0,1,1,1]
]
输出:15
解释:
边长为 1 的正方形有 10 个。
边长为 2 的正方形有 4 个。
边长为 3 的正方形有 1 个。
正方形的总数 = 10 + 4 + 1 = 15.
动态规划
def countSquares(self, matrix: List[List[int]]) -> int:
m, n = len(matrix), len(matrix[0])
f = [[0] * n for _ in range(m)]
ans = 0
for i in range(m):
for j in range(n):
if i == 0 or j == 0: # 边界条件的判断。
f[i][j] = matrix[i][j]
elif matrix[i][j] == 0:
f[i][j] = 0
else:
f[i][j] = min(f[i][j-1], f[i-1][j], f[i-1][j-1]) + 1
# 要注意的是 这个1 是加在外面的。代表着只要另外三个角的元素都是1,则就可以组成一个大正方形。
ans += f[i][j]
return ans
回文子串
判断给定字符串,有多少个回文子串。其中一个字母也算是回文子串。
解题思路:采用动态规划的方法。维护一个动态数组,代表s[i:j]
是不是回文子串。
代码如下:
def countSubstrings(self, s: str) -> int:
# dp[i][j] 代表子串[i,j]是否是一个子串
n = len(s)
dp = [[False] * n for _ in range(n)]
count = 0
# 枚举所有可能,因为代表子串,所以 i<=j
for j in range(n):
for i in range(0, j+1):
length = j-i + 1
if length == 1:
dp[i][j] = True
count += 1
if length == 2 and s[i] == s[j]:
dp[i][j] = True
count += 1
if length > 2 and s[i] == s[j] and dp[i+1][j-1] is True:
dp[i][j] = True
count += 1
return count