代码随想录算法训练营第四十三天 | 300.最长递增子序列 674.最长连续递增序列 718.最长重复子数组

LeetCode 300.最长递增子序列:

文章链接
题目链接:300.最长递增子序列

思路:

需要分析题目看懂题目要求的子序列的定义
还是使用动态规划,动规五部曲

  • dp数组及含义:
    dp[i]表示[0, i]结尾元素为nums[i]的最长递增子序列长度(要规定结尾元素为nums[i]以便递推时比较新元素是加入序列还是新序列
    需要注意的是,dp数组这样规定后,那么nums数组的最长递增子序列的长度不一定在dp[-1],而是需要dp数组中求最大值
  • 递推式:
    • 最开始想到的是dp[i]与dp[i - 1]做递推,但是如果nums[i] <= nums[i - 1],dp[i]就不知道怎么赋值了,如果dp[i]赋值为1,也不对,因为前面[0, i-2]可能有能让nums[i]加入的子序列。
    • 因此想到dp[i]与dp[j]做递推,j ∈[0, i - 1],也就是在 j 的范围内找结尾元素的值 < nums[i]且长度最长的子序列,然后将nums[i]加入该子序列。
if nums[i] > nums[j]:
	dp[i] = max(dp[i], dp[j] + 1)
  • 初始化:
    由于dp数组的定义,所以dp全部初始化为1
  • 遍历顺序
    由递推公式有,遍历顺序应当是从前往后
  • 举例
    最后结果是dp数组求最大值
    在这里插入图片描述
    代码
class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        len_nums = len(nums)
        if len_nums <= 1:	# 这里要区分一下
            if len_nums == 0:
                return 0
            return 1
        # 初始化
        dp = [1] * len_nums
        result = 1  # 记录最大子序列长度
        # 遍历
        for i in range(1, len_nums):
            for j in range(i):  # 在[0, i-1]的子序列中找结尾元素 < nums[i]且长度最长的
                if nums[i] > nums[j]: dp[i] = max(dp[i], dp[j] + 1) 
            if dp[i] > result:  # 最大长度要在dp数组中再求max
                result = dp[i]
        return result
        

LeetCode 674.最长连续递增序列:

文章链接
题目链接:674.最长连续递增序列

思路:

首先分析题目,要求得到的是最长连续递增子序列,其中的元素是严格递增,且子序列中的元素在nums数组中是相邻的。
与上面题目相似,但是由于要求子序列元素在nums数组中是相邻的,所以递推式只需要比较nums[i]和nums[i - 1]即可。(或者说,如果符合条件的话,nums[i]只能接到nums[i - 1]的序列之后
动规五部曲:

  • dp数组及含义:
    dp[i]:[0, i]结尾元素为nums[i]的最长连续递增子序列的长度.
    同时也是,要求的最长连续递增子序列的长度是dp数组求max,不一定是dp[-1]的值
  • 递推式
    dp[i]由dp[i - 1]推导来
if nums[i] > nums[i - 1]:
	dp[i] = dp[i - 1] + 1
  • 初始化:
    dp数组全部初始化为1
  • 遍历顺序
    由递推公式有,遍历顺序为从前往后
  • 举例
    在这里插入图片描述
    代码:
class Solution:
    def findLengthOfLCIS(self, nums: List[int]) -> int:
        len_nums = len(nums)
        if len_nums <= 1:
            if len_nums == 0:
                return 0
            return 1
        # 初始化
        dp = [1] * len_nums
        result = 1  # 保存最大值
        # 遍历
        for i in range(1, len_nums):
            if nums[i] > nums[i - 1]:
                dp[i] = dp[i - 1] + 1
            if dp[i] > result:
                result = dp[i]
        return result

        

LeetCode 718.最长重复子数组:

文章链接
题目链接:718.最长重复子数组

思路:

首先分析题目:题目中实际要求的是得到公共连续最长子数组,只要想到动态规划二维dp数组保存两个数组匹配的所有情况。
动规五部曲:

  • dp数组及含义:
    dp[i][j]:A[0, i - 1]与B[0, j - 1]的结尾为A[i-1]和B[j-1]的最长公共连续子数组的长度。
    dp[i][j]对应A[i-1]和B[j-1]会在后面说明
  • 递推
    由dp含义可知,dp[i][j]由dp[i - 1][j - 1]推导而来(或者说就是判断A[i-1]和B[j-1]是否相等
if A[i-1] == B[j-1]:
	dp[i][j] = dp[i-1][j-1] + 1
  • 初始化
    初始化为全0。
    在此解释为什么dp[i][j]对应A[i-1]和B[j-1],如果dp[i][j]对应A[i]和B[j],那么初始化时,dp[0][0]需要单独判断,且递推公式dp[i][j]需要由dp[i-1][j-1]推导得到,那么还需要判断第0行和第0列的数值。也就是需要单独判断第0行和第0列,但是如果在外面加一圈的话,只需要初始化为全0,第1行和第1列被包括在循环中。
  • 遍历顺序
    先行后列 or 先列后行均可,但是都要从前往后遍历,因为递推要用到dp[i-1][j-1]
  • 举例
    最终结果需要在dp数组中求max
    在这里插入图片描述
class Solution:
    def findLength(self, nums1: List[int], nums2: List[int]) -> int:
        len_n1, len_n2 = len(nums1), len(nums2)
        # 初始化
        dp = [[0] * (len_n2 + 1)for _ in range(len_n1 + 1)]  # 外面包了一圈
        result = 0
        # 遍历
        for i in range(1, len_n1 + 1): # 这里 i, j 范围为[1, len]
            for j in range(1, len_n2 + 1):
                if nums1[i - 1] == nums2[j - 1]:
                    dp[i][j] = dp[i - 1][j - 1] + 1
                if dp[i][j] > result:
                    result = dp[i][j]
        return result
        

压缩二维dp数组为一维

  • 可以按行压缩,或者按列压缩
    在这里插入图片描述在这里插入图片描述
  • 以按行压缩为例,有需要注意的地方
    • dp[j]依赖于上一行的dp[j-1],如果顺序遍历 j ,那么dp[j-1]就被覆盖为这一行的dp[j-1]了,所以要逆序遍历
    • 递推公式也有区别,原来二维dp,当A[i-1] != B[j-1]时不需要操作,但是一维需要设置dp[j] = 0,不然的话dp[j]会继承上一行的dp[j],那么结果就不对了
'''
按行压缩二维dp数组需要注意的地方:
1. j需要逆序遍历,二维dp[i][j]要用到dp[i-1][j-1],对应一维就是dp[j]用到上一行的dp[j-1],
如果顺序遍历的话,dp[j-1]就变成这一行的,覆盖了上一行的dp[j-1],所以要逆序
2. 递推公式改变,二维是if nums1[i-1] == nums2[j-1]:dp[i][j] = dp[i-1][j-1],
但是一维应当是if 不变,else dp[j] = 0,不然的话明明结尾元素不相同,长度应当为0,但是dp[j]继承了上一行的数值,可能不是0了
'''
class Solution:
    def findLength(self, nums1: List[int], nums2: List[int]) -> int:
        len_n1, len_n2 = len(nums1), len(nums2)
        # 初始化,按行压缩为一维数组
        dp = [0] * (len_n2 + 1)
        result = 0
        # 遍历
        for i in range(1, len_n1 + 1):
            for j in range(len_n2, 0, -1):  # j要逆序遍历,以免覆盖dp[i-1][j-1]的数值
                if nums1[i - 1] == nums2[j - 1]:
                    dp[j] = dp[j - 1] + 1
                else:
                    dp[j] = 0
                if dp[j] > result:
                    result = dp[j]
        return result

学习收获:

首先是最长递增子序列和最长连续递增子序列:

  • 区别在于子序列中相邻的元素在原数组中是否相邻,从而递推公式中是只能在nums[i-1]后添加元素nums[i]还是num[0: i ]中找元素值小且最长的子序列添加元素nums[i]
  • 同时还要注意是否严格递增,就是相邻的元素,后面的值是否一定大于前面的值,不能相等
  • 最后dp数组的含义是,以nums[i]为结尾的子序列的最大长度(还有其它条件可以加上)

然后是两个数组的最长公共连续子数组:

  • 要想到二维dp数组保存匹配的所以结果,同时因为是连续的,所以dp[i][j]只依赖于dp[i-1][j-1],
  • 以及为了便于初始化,dp的大小为(len1 + 1)*(len2 + 1),dp[i][j]对应A[i-1]和B[j-1]
  • 同时按行 / 按列压缩为一维时,需要注意列逆序 / 行逆序,同时元素值不相等时dp要赋值为0
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值