leetcode(6)子序列系列题目
动态规划:
子序列(不连续)
(1)300. 最长递增子序列
注意:可以不连续
dp[i]表示i之前包括i的以nums[i]结尾最长上升子序列的长度
状态转移方程:位置i的最长升序子序列等于j从0到i-1各个位置的最长升序子序列 + 1 的最大值。
所以:if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);
即将dp[i]更新为dp[j] + 1的最大值。
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
dp = [1] * len(nums)
for i in range(len(nums)):
for j in range(i):
if nums[i] > nums[j]:
dp[i] = max(dp[i], dp[j] + 1)
return max(dp)
(2)1143. 最长公共子序列
1035. 不相交的线 与本题实质上是同一个题目
注意:与 718. 最长重复子数组 的区别在于这里不要求是连续的了,但要有相对顺序,即:"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。
dp[i][j]:长度为[0, i - 1]的字符串text1与长度为[0, j - 1]的字符串text2的最长公共子序列为dp[i][j]
- 如果text1[i - 1] 与 text2[j - 1]相同,那么找到了一个公共元素,所以dp[i][j] = dp[i - 1][j - 1] + 1;
- 如果text1[i - 1] 与 text2[j - 1]不相同,那就看看text1[0, i - 2]与text2[0, j - 1]的最长公共子序列 和 text1[0, i - 1]与text2[0, j - 2]的最长公共子序列,取最大的。
即:dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
len1, len2 = len(text1), len(text2)
dp = [[0] * (len2 + 1) for _ in range(len1 + 1)]
for i in range(1, len1 + 1): # 记得+1!!!
for j in range(1, len2 + 1):
if text1[i - 1] == text2[j - 1]:
dp[i][j] = dp[i - 1][j - 1] + 1
else:
dp[i][j] = max(dp[i][j - 1], dp[i - 1][j])
return dp[-1][-1]
子数组(连续)
(3)674. 最长连续递增序列
注意:与 300. 最长递增子序列 的区别在于这个连续,不需要两个for循环
class Solution:
def findLengthOfLCIS(self, nums: List[int]) -> int:
res = 1
n = len(nums)
if n == 1:return 1
dp = [1] * n
for i in range(1, n):
if nums[i] > nums[i - 1]:
dp[i] = dp[i - 1] + 1
res = max(res, dp[i])
return res
(4)718. 最长重复子数组
dp[i][j] :以下标i - 1为结尾的A,和以下标j - 1为结尾的B,最长重复子数组长度为dp[i][j]。
当A[i - 1] 和B[j - 1]相等的时候,dp[i][j] = dp[i - 1][j - 1] + 1;
注意:与 1143. 最长公共子序列 的区别在于A[i - 1] 和B[j - 1]不相等的时候,从0开始计算,因此不需要判断
class Solution:
def findLength(self, nums1: List[int], nums2: List[int]) -> int:
len1, len2 = len(nums1), len(nums2)
dp = [[0] * (len2 + 1) for _ in range(len1 + 1)]
res = 0
for i in range(1, len1 + 1):
for j in range(1, len2 + 1):
if nums1[i - 1] == nums2[j - 1]:
dp[i][j] = dp[i - 1][j - 1] + 1
res = max(res, dp[i][j])
return res
(5)53. 最大子数组和
这道题目的思想是: 走完这一生 如果我和你在一起会变得更好,那我们就在一起,否则我就丢下你。 我回顾我最光辉的时刻就是和不同人在一起,变得更好的最长连续时刻
如果前边累加后还不如自己本身大,那就把前边的都扔掉,从此自己本身重新开始累加。
如果加入当前元素后的cur < 没有加之前的res,则不会添加当前元素
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
res = cur = nums[0]
for i in range(1, len(nums)):
cur += nums[i]
cur = max(cur, nums[i])
res = max(res, cur)
return res
编辑距离
(6)392. 判断子序列
注意:与 1143. 最长公共子序列 的区别是这里是判断s是否为t的子序列,即t的长度是大于等于s的。
如果s[i - 1] 与 t[j - 1]不相同,只要看s[i - 1]与 t[j - 2]的比较结果,即:dp[i][j] = dp[i][j - 1];
编辑距离的入门题目
class Solution:
def isSubsequence(self, s: str, t: str) -> bool:
len1, len2 = len(s), len(t)
dp = [[0] * (len2 + 1) for _ in range(len1 + 1)]
for i in range(1, len1 + 1):
for j in range(1, len2 + 1):
if s[i - 1] == t[j - 1]:
dp[i][j] = dp[i - 1][j - 1] + 1
else:
dp[i][j] = dp[i][j - 1]
return dp[-1][-1] == len(s)
(7)115. 不同的子序列
注意:与 392. 判断子序列 的区别是这里是计算在 s 的子序列中 t 出现的个数,即s的长度是大于等于t的。
dp[i][j]:以i-1为结尾的s子序列中出现以j-1为结尾的t的个数为dp[i][j]。
-
当s[i - 1] 与 t[j - 1]相等时,dp[i][j]可以有两部分组成。
- 一部分是用s[i - 1]来匹配,那么个数为dp[i - 1][j - 1]。
- 一部分是不用s[i - 1]来匹配,个数为dp[i - 1][j]。
所以dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
-
当s[i - 1] 与 t[j - 1]不相等时,dp[i][j]只有一部分组成,不用s[i - 1]来匹配,即:dp[i][j] = dp[i - 1][j];
dp[i][0] 表示:以i-1为结尾的s可以随便删除元素,出现空字符串的个数。
那么dp[i][0]一定都是1,因为也就是把以i-1为结尾的s,删除所有元素,出现空字符串的个数就是1。
class Solution:
def numDistinct(self, s: str, t: str) -> int:
len1, len2 = len(s), len(t)
dp = [[0] * (len2 + 1) for _ in range(len1 + 1)]
for i in range(len1 + 1):
dp[i][0] = 1
for i in range(1, len1 + 1):
for j in range(1, len2 + 1):
if s[i - 1] == t[j - 1]:
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]
else:
dp[i][j] = dp[i - 1][j]
return dp[-1][-1]
(8)583. 两个字符串的删除操作
注意: 与 115.不同的子序列 的区别是两个字符串都可以删除了
dp[i][j]:以i-1为结尾的字符串word1,和以j-1位结尾的字符串word2,想要达到相等,所需要删除元素的最少次数。
-
当word1[i - 1] 与 word2[j - 1]相同的时候,dp[i][j] = dp[i - 1][j - 1];
-
当word1[i - 1] 与 word2[j - 1]不相同的时候,有三种情况:
- 情况一:删word1[i - 1],最少操作次数为dp[i - 1][j] + 1
- 情况二:删word2[j - 1],最少操作次数为dp[i][j - 1] + 1
- 情况三:同时删word1[i - 1]和word2[j - 1],操作的最少次数为dp[i - 1][j - 1] + 2
最后取最小值:dp[i][j] = min({dp[i - 1][j - 1] + 2, dp[i - 1][j] + 1, dp[i][j - 1] + 1});
dp[i][0]:word2为空字符串,以i-1为结尾的字符串word1要删除多少个元素,才能和word2相同呢,很明显dp[i][0] = i。dp[0][j]同理
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
len1, len2 = len(word1), len(word2)
dp = [[0] * (len2 + 1) for _ in range(len1 + 1)]
for i in range(len1 + 1):
dp[i][0] = i
for j in range(len2 + 1):
dp[0][j] = j
for i in range(1, len1 + 1):
for j in range(1, len2 + 1):
if word1[i - 1] == word2[j - 1]:
dp[i][j] = dp[i - 1][j - 1]
else:
dp[i][j] = min(dp[i - 1][j - 1] + 2, dp[i - 1][j] + 1, dp[i][j - 1] + 1)
return dp[-1][-1]
(9)72. 编辑距离
注意: 与 115.不同的子序列 的区别是删除变成3种操作;与 583. 两个字符串的删除操作 的区别是只能对一个字符串,但是可以进行3种操作
-
当word1[i - 1] 与 word2[j - 1]不相同的时候,有三种情况:
- 情况一:删word1[i - 1],最少操作次数为dp[i - 1][j] + 1
- 情况二:删word2[j - 1],最少操作次数为dp[i][j - 1] + 1(word2添加一个元素,相当于word1删除一个元素)
- 情况三:替换操作,操作的最少次数为dp[i - 1][j - 1] + 1
最后取最小值:dp[i][j] = min({dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]}) + 1;
dp[i][0] :以下标i-1为结尾的字符串word1,和空字符串word2,最近编辑距离为dp[i][0]。
那么dp[i][0]就应该是i,对word1里的元素全部做删除操作,即:dp[i][0] = i;dp[0][j]同理
2023.06.08百度文库一面
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
len1, len2 = len(word1), len(word2)
dp = [[0] * (len2 + 1) for _ in range(len1 + 1)]
for i in range(len1 + 1):
dp[i][0] = i
for j in range(len2 + 1):
dp[0][j] = j
for i in range(1, len1 + 1):
for j in range(1, len2 + 1):
if word1[i - 1] == word2[j - 1]:
dp[i][j] = dp[i-1][j-1]
else:
dp[i][j] = min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) + 1
return dp[-1][-1]
(10)44. 通配符匹配
class Solution:
def isMatch(self, s: str, p: str) -> bool:
len1, len2 = len(s), len(p)
dp = [[False] * (len2 + 1) for _ in range(len1 + 1)]
dp[0][0] = True
# s是空字符串时,p可以匹配的位置
for j in range(1, len2 + 1):
if p[j - 1] == '*':
dp[0][j] = dp[0][j - 1]
else:
break
for i in range(1, len1 + 1):
for j in range(1, len2 + 1):
if p[j - 1] == '*':
dp[i][j] = dp[i - 1][j] or dp[i][j - 1]
elif p[j - 1] == '?' or p[j - 1] == s[i - 1]:
dp[i][j] = dp[i - 1][j - 1]
return dp[-1][-1]
贪心算法:
(2)674. 最长连续递增序列
其实就是动态规划的dp数组改成一个变量来存
class Solution:
def findLengthOfLCIS(self, nums: List[int]) -> int:
res = cur = 1
n = len(nums)
if n == 1:return 1
for i in range(1, n):
if nums[i] > nums[i - 1]:
cur += 1
else:
cur = 1
res = max(res, cur)
return res
(5)53. 最大子数组和
双指针:
(6)392. 判断子序列
参考资料:
动态规划之编辑距离总结篇