0
点赞
收藏
分享

微信扫一扫

leetcode(力扣) 718. 最长重复子数组 & 1143. 最长公共子序列 & 1035. 不相交的线 (动态规划)


文章目录

  • ​​718. 最长重复子数组​​
  • ​​题目描述​​
  • ​​思路分析​​
  • ​​完整代码​​
  • ​​1143. 最长公共子序列​​
  • ​​1035. 不相交的线:​​

这三道题思路基本一样,尤其是最后两道,代码都一样,一点不需要改的,所以放一起把。

718. 最长重复子数组

题目描述

给两个整数数组 nums1 和 nums2 ,返回 两个数组中 公共的 、长度最长的子数组的长度 。

示例 1:
输入:nums1 = [1,2,3,2,1], nums2 = [3,2,1,4,7]
输出:3
解释:长度最长的公共子数组是 [3,2,1] 。

示例 2:
输入:nums1 = [0,0,0,0,0], nums2 = [0,0,0,0,0]
输出:5

思路分析

一道一眼看上去就得用动态规划的题目。

老规矩,五步走:

1.确定dp下标含义:

dp[i][j] 表示数组A到下标i-1,数组B到下标j-1,时的公共最长子数组的长度。

这是力扣上大多数题解这么说的,不过有没有发现,为什么是到i-1 j-1,如果不看题解,自己做的话拿到这道题,dp定义肯定是 数组A到下标i,数组B到下标j,时的公共最长子数组的长度。

所以为啥是这样?后面再说。

2.状态转移公式:

根据上面定义的dp数组含义,很容易想到,dp[i][j]的状态就看前一个nums[i-1] 是不是等于 nums[j-1] 如果等于,则dp[i][j] = dp[i-1][j-1]+1。

偷张图,助于理解。

leetcode(力扣) 718. 最长重复子数组 & 1143. 最长公共子序列 & 1035. 不相交的线 (动态规划)_子数组

3.初始化

直接初始化成全0就行,观察推导公式,严格来说,只需要初始化第一行和第一列为0,其余初始化成什么都无所谓。

4.遍历顺序

因为第一行和第一列已经初始化了,所以先遍历行还是列都无所谓。正着来就行了。

所以!!!!!
所以!!!!!
所以!!!!!

为什么在初始化dp数组的时候是:
数组A到下标i-1,数组B到下标j-1,时的公共最长子数组的长度 。(情况一)
而不是:
数组A到下标i,数组B到下标j,时的公共最长子数组的长度 。(情况二)

我看力扣上压根没人说,其实这道题的关键才是在这里,因为拿到手肯定会按情况二去定义。

来看一下如果说定义成情况二会发生什么?

原来的第一行和第一列是没有数字的,因为dp[0][0] 表示 数组A到下标-1?????
显然是没意义的,所以第一行和第一列都初始化成了0.

而情况二的时候,第一行和第一列都是有意义的,所以初始化这里要一点一点去写了。

即:第一行所有数字,如果和第一列第一个数相等,则赋值1,否则为0.
第一列所有数字,如果和第一行第一个数字相等,则赋值1,否则0.

这块看上面那个二维数组的图就秒懂。

# 初始化第一行。
for i in range(len(nums1)):
if nums2[0] == nums1[i]:
dp[0][i] = 1
res = 1
# 初始化第一列
for i in range(len(nums2)):
if nums1[0] == nums2[i]:
dp[i][0] = 1
res = 1

完整代码

dp[i][j] 含义为 数组A到下标i-1,数组B到下标j-1,时的公共最长子数组的长度  。

class Solution:
def findLength(self, nums1: List[int], nums2: List[int]) -> int:
# dp[i][j] 表示 两数组均到i-1时的最长公共子数组。


dp = [[0 for _ in range(len(nums2)+1)] for _ in range(len(nums1)+1)]
res = 0
for i in range(1,len(nums1)+1):
for j in range(1,len(nums2)+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

dp含义为:数组A到下标i,数组B到下标j,时的公共最长子数组的长度 。(情况二)
class Solution:
def findLength(self, nums1: List[int], nums2: List[int]) -> int:
# dp[i][j] 表示 两数组均到i时的最长公共子数组。
dp = [[0 for _ in range(len(nums1))] for _ in range(len(nums2))]
res = 0
# 初始化第一行。
for i in range(len(nums1)):
if nums2[0] == nums1[i]:
dp[0][i] = 1
res = 1
# 初始化第一列
for i in range(len(nums2)):
if nums1[0] == nums2[i]:
dp[i][0] = 1
res = 1

for i in range(1,len(nums2)):
for j in range(1,len(nums1)):
if nums1[j] == nums2[i]:
dp[i][j] = dp[i-1][j-1]+1
res = max(res,dp[i][j])
return res

1143. 最长公共子序列

这道题和上面那一道题其实是类似的。

这里的子序列是可以不连续的,而子数组是需要连续的。

1.dp数组含义
跟上一题相同的地方是dp数组的含义,同样为了方便定义成:dp[i][j] 表示 A字符串到下标为i-1 和 B字符串到下标为 j-1 的公共最长子序列的长度。

跟上一题一样的问题,为啥不定义成下标到 i 。答案也是一样的,因为如果定义成:dp[i][j] 表示 A字符串到下标为i 和 B字符串到下标为 j 的公共最长子序列的长度,那就得初始化第一行和第一列。 定义成i-1,就全初始化成0就行了。

定义成 i 的初始化:

# 初始化第一行。
for i in range(len(nums1)):
if nums2[0] == nums1[i]:
dp[0][i] = 1
else:
dp[0][i] =dp[0][i-1]

# 初始化第一列
for i in range(len(nums2)):
if nums1[0] == nums2[i]:
dp[i][0] = 1
else:
dp[i][0] = dp[i-1][0]

2.状态转移方程

首先肯定还是得比较 text1[i - 1] 和 text2[j - 1] 是否相等。

  • 当 text1[i - 1] == text2[j - 1] 时,说明两个子字符串的最后一位相等,所以最长公共子序列又增加了 1,所以 dp[i][j] = dp[i - 1][j - 1] + 1;举个例子,比如对于 ac 和 bc 而言,他们的最长公共子序列的长度等于 a 和 b 的最长公共子序列长度 0 + 1 = 1。
  • 当 text1[i - 1] != text2[j - 1] 时,说明两个子字符串的最后一位不相等,那么此时的状态 dp[i][j] 应该是 dp[i - 1][j] 和 dp[i][j - 1] 的最大值。举个例子,比如对于 ace 和 bc 而言,他们的最长公共子序列的长度等于 ① ace 和 b 的最长公共子序列长度0 与 ② ac 和 bc 的最长公共子序列长度1 的最大值,即 1。

这里再解释一下不等于的情况,假设还是 ace和bc,e不等于c,则保底的情况就是倒退到没遍历到c的时候的最大长度,也就是ace和b。还有另一个情况就是加入c但是倒退ace,变成ac和bc。这两种取其中长的作为答案。
这块画成二维数组更好理解。

完整代码

定义成i-1:
class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
# dp[i][j] 表示 两字符串均到i-1时的最长公共子串。

dp = [[0 for _ in range(len(text2)+1)] for _ in range(len(text1)+1)]
for i in range(1,len(text1)+1):
for j in range(1,len(text2)+1):
if text1[i-1] == text2[j-1]:
dp[i][j] = dp[i-1][j-1]+1
else:
dp[i][j] = max(dp[i-1][j],dp[i][j-1])

print(dp[-1][-1])
return dp[-1][-1]
定义成i:
class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
nums1 = text1
nums2 = text2

# dp[i][j] 表示 两字符串均到i时的最长公共子串。
dp = [[0 for _ in range(len(nums1))] for _ in range(len(nums2))]
res = 0
# 初始化第一行。
for i in range(len(nums1)):
if nums2[0] == nums1[i]:
dp[0][i] = 1
else:
dp[0][i] =dp[0][i-1]

# 初始化第一列
for i in range(len(nums2)):
if nums1[0] == nums2[i]:
dp[i][0] = 1
else:
dp[i][0] = dp[i-1][0]

for i in range(1,len(nums2)):
for j in range(1,len(nums1)):
if nums1[j] == nums2[i]:
dp[i][j] = dp[i-1][j-1]+1
else:
dp[i][j] = max(dp[i-1][j],dp[i][j-1])
return dp[-1][-1]

1035. 不相交的线:

这道题看图:

leetcode(力扣) 718. 最长重复子数组 & 1143. 最长公共子序列 & 1035. 不相交的线 (动态规划)_初始化_02


题目中这连线相同的值,并且说这个连线不能相交,这不是在找数组A和数组B的公共子序列嘛。

只不过这里有个不能相交的规定,那就是子序列在两个数组里的相对位置是一样的嘛。
比如 142 和124,子序列为14,在A中 顺序是1 然后4,在数组B中也是先1后4。
其实转化问题之后,这道题就是在求数组A和数组B的公共子序列。

和上一个 1143题一模一样,连代码都不用改,直接怼两道题。

当然,变量名得改一下。

class Solution:
def maxUncrossedLines(self, nums1: List[int], nums2: List[int]) -> int:
# dp[i][j] 表示A数组下标到i-1 B数组下标到i-1 时的公共最长子序列

dp = [[0 for _ in range(len(nums2) + 1)] for _ in range(len(nums1) + 1)]
for i in range(1, len(nums1) + 1):
for j in range(1,len(nums2) + 1):
if nums1[i-1] == nums2[j-1]:
dp[i][j] = dp[i - 1][j - 1] + 1
else:
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
print(dp[-1][-1])
return dp[-1][-1]


举报

相关推荐

0 条评论