研习代码 day45 | 动态规划——子序列问题

一、最长递增子序列

        1.1 题目

        给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

        子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。 

示例 1:

输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

示例 2:

输入:nums = [0,1,0,3,2,3]
输出:4

示例 3:

输入:nums = [7,7,7,7,7,7,7]
输出:1

提示:

  • 1 <= nums.length <= 2500
  • -10^4 <= nums[i] <= 10^4

进阶:

  • 你能将算法的时间复杂度降低到 O(n log(n)) 吗?

        1.2 题目链接

        300.最长递增子序列

        1.3 解题思路和过程想法

        (1)解题思路

        # 分析:当前状态受前一状态影响,且遵守类似规则的求解问题,动态规划问题
                      因为该子序列不一定是连续的,但需判断其与当前元素的关系,所以使用两层循环
                      因为只是判断相等关系,可能存在多次相同匹配,在过程中用result记录区间最大值

        # 数组:截至到 i 的最长严格递增子序列的长度 dp[i]

        # 递推关系:因为存在一个累计过程,如果满足当前元素递增 dp[i] = max(dp[i], dp[j]+1)

        # 初始化:每个元素位置的初始递增长度都是 1

        (2)过程想法

        有框架之后,代码很好写

        1.4 代码

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        # 分析:当前状态受前一状态影响,且遵守类似规则的求解问题,动态规划问题

        # 数组:截至到 i 的最长严格递增子序列的长度 dp[i]

        # 递推关系:如果满足当前元素递增 dp[i] = max(dp[i], dp[i-1]+1)

        # 初始化:每个元素位置的初始递增长度都是 1 
        length = len(nums)

        dp = [1] * length
        result = 1                      

        for i in range(1, length):      # 遍历当前节点的判断
            for j in range(0, i):       # 遍历当前节点之前的所有结点
                if nums[i] > nums[j]:
                    dp[i] = max(dp[i], dp[j]+1)
            result = max(result, dp[i])

        return result
            

二、最长连续递增子序列

        2.1 题目

        给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。

        连续递增的子序列 可以由两个下标 l 和 rl < r)确定,如果对于每个 l <= i < r,都        有 nums[i] < nums[i + 1] ,那么子序列 [nums[l], nums[l + 1], ..., nums[r - 1], nums[r]] 就是连续递增子序列。

示例 1:

输入:nums = [1,3,5,4,7]
输出:3
解释:最长连续递增序列是 [1,3,5], 长度为3。
尽管 [1,3,5,7] 也是升序的子序列, 但它不是连续的,因为 5 和 7 在原数组里被 4 隔开。 

示例 2:

输入:nums = [2,2,2,2,2]
输出:1
解释:最长连续递增序列是 [2], 长度为1。

提示:

  • 1 <= nums.length <= 10^4
  • -10^9 <= nums[i] <= 10^9

        2.2 题目链接

        674.最长连续递增序列

        2.3 解题思路和过程想法

        (1)解题思路

        # 分析:当前状态受前一状态影响,且遵守类似规则的求解问题,动态规划问题
                      因为该子序列是连续的,用一层循环处理即可
                      因为只是判断相等关系,可能存在多次相同匹配,在过程中用result记录区间最大值

        # 数组:截至到 i 的最长严格递增子序列的长度 dp[i]

        # 递推关系:如果满足当前元素递增 dp[i] = dp[j]+1

        # 初始化:每个元素位置的初始递增长度都是 1

        (2)过程想法

        思路很好想,代码也很好写

        2.4 代码

class Solution:
    def findLengthOfLCIS(self, nums: List[int]) -> int:
        # 分析:当前状态受前一状态影响,且遵守类似规则的求解问题,动态规划问题

        # 截至到 i 的最长连续递增子序列的长度 cur

        # 递推关系:如果满足当前元素递增 cur = cur+1

        # 初始化:每个元素位置的初始递增长度都是 1 
        length = len(nums)

        # 利用其迭代过程,节省存储空间
        cur = 1
        result = 1

        for i in range(1, length):
            if nums[i] > nums[i-1]:
                cur = cur+1
                result = max(result, cur)
            else:
                cur = 1

        return result

三、最长重复子数组

        3.1 题目

        给两个整数数组 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 <= nums1.length, nums2.length <= 1000
  • 0 <= nums1[i], nums2[i] <= 100

        3.2 题目链接

        718.最长重复子数组

        3.3 解题思路和过程想法

        (1)解题思路

        # 分析:存在需比较的两个数组,如果使用暴力法,其实是需要三层for循环的,根据数据的量级,是会提示超时的;当前状态受前一状态影响,且遵守类似规则的求解问题,动态规划问题。但需标记两个数组的结束指针,所以考虑使用二维DP数组。
         因为只是判断相等关系,可能存在多次相同匹配,在过程中用result记录区间最大值

        # 数组:nums1 截至到 i , nums2 截至到 j 的最长重复子序列的长度 dp[i][j]

        # 递推关系:如果当前元素满足要求 dp[i][j] = dp[i-1][j-1]+1

        # 初始化:每个元素位置的初始递增长度都是 0,但因为使用的判断条件是 nums1[i-1] 和 nums2[j-1],所以需先初始化第一行与第一列。

        注:也可以使用一维滚动数组的思想,思路是类同的。

        (2)过程想法

       需要想到使用二维数组,后续的代码是好写的。

        3.4 代码

        3.4.1 二维动态数组
class Solution:
    def findLength(self, nums1: List[int], nums2: List[int]) -> int:
        # 分析:当前状态受前一状态影响,且遵守类似规则的求解问题,动态规划问题

        # nums1 截至到 i , nums2 截至到 j 的最长重复子序列的长度 dp[i][j]

        # 递推关系:如果当前元素满足要求 dp[i][j] = dp[i-1][j-1]+1

        # 初始化:每个元素位置的初始递增长度都是 0
        dp = [[0] * (len(nums2)+1) for _ in range(len(nums1)+1)]

        # 对第一行和第一列进行初始化
        for i in range(len(nums1)):
            if nums1[i] == nums2[0]:
                dp[i + 1][1] = 1
        for j in range(len(nums2)):
            if nums1[0] == nums2[j]:
                dp[1][j + 1] = 1

        result = 0

        # 此处用的是 i-1 与 j-1 相比较,其实用 i 和 j 比较也可以,但是因为下标的原因,前者更简单
        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
                result = max(result, dp[i][j])

        return result
        3.4.2 一维滚动数组 
class Solution:
    def findLength(self, nums1: List[int], nums2: List[int]) -> int:
        # 分析:当前状态受前一状态影响,且遵守类似规则的求解问题,动态规划问题

        # nums1 截至到 i , nums2 截至到 j 的最长重复子序列的长度 dp[i][j]

        # 递推关系:如果当前元素满足要求 dp[i][j] = dp[i-1][j-1]+1

        # 初始化:为减少空间复杂度,采用一维滚动数组的思想存储过程变量。
        # 每个元素位置的初始递增长度都是 0
        dp = [0] * (len(nums2) + 1)
        result = 0

        # 此处用的是 i-1 与 j-1 相比较,其实用 i 和 j 比较也可以,但是因为下标的原因,前者更简单
        for i in range(1, len(nums1) + 1):
            pre = 0
            for j in range(1, len(nums2) + 1):
                cur = dp[j]
                if nums1[i - 1] == nums2[j - 1]:    # 如果找到相应的元素,则更迭dp数组
                    dp[j] = pre + 1          
                    result = max(dp[j], result)
                else:                               # 否则,结束更迭,重置dp数组
                    dp[j] = 0
                pre = cur

        return result

你可能感兴趣的:(动态规划,算法,数据结构,python,leetcode)