代码随想录算法训练营 Day 38

2 minute read

Published:

动态规划五部曲

  1. 确定dp数组以及下标的含义
  2. 确定递推公式
  3. 初始化dp数组
  4. 确定遍历顺序
  5. 举例推导dp数组

第5步随着输入不同而变化,因此在此省去

Q1. LeetCode 1143

与LeetCode 718类似,但这里不再要求连续,只是要求相对顺序一致,所以对于初始化和状态转换都有影响

前四步:

  1. 使用二维的dp数组,dp[i][j]表示text1[:i+1]和text2[:j+1]之间的最长公共子序列的长度
  2. 递推公式:1. 两个字符相同,$dp[i][j] = dp[i - 1][j - 1] + 1$;2. 两个字符不同,本题依然要更新长度,因此 $dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])$
  3. 初始化:整体初始化为0,然后更新第一行和第一列,只要能找到两个位置相同,那么除了当前位置要修改,之后的所有位置都需要设置成1(有一个长度为1的子序列)
  4. 遍历顺序:两层循环均从前向后遍历
class Solution:
    def longestCommonSubsequence(self, text1: str, text2: str) -> int:
        n1 = len(text1)
        n2 = len(text2)

        dp = [[0] * n1 for _ in range(n2)]
        for i in range(n2):
            if text1[0] == text2[i]:
                dp[i][0] = 1
            else:
                if i > 0:
                    dp[i][0] = dp[i - 1][0]

        for j in range(n1):
            if text2[0] == text1[j]:
                dp[0][j] = 1
            else:
                if j > 0:
                    dp[0][j] = dp[0][j - 1]
        

        for i in range(1, n2):
            for j in range(1, n1):
                if text2[i] == text1[j]:
                    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]

复杂度分析

时间复杂度:O($n \times m$) 空间复杂度:O($n \times m$)

Q2. LeetCode 1035

可以看到不相交的连线数,就是要找到一个相对顺序一致的最长子序列。因为如果顺序交错的话,线段一定会相交。那么和Q1相同

class Solution:
    def maxUncrossedLines(self, nums1: List[int], nums2: List[int]) -> int:
        n1 = len(nums1)
        n2 = len(nums2)

        dp = [[0] * n1 for _ in range(n2)]
        for i in range(n2):
            if nums1[0] == nums2[i]:
                dp[i][0] = 1
            else:
                if i > 0:
                    dp[i][0] = dp[i - 1][0]

        for j in range(n1):
            if nums2[0] == nums1[j]:
                dp[0][j] = 1
            else:
                if j > 0:
                    dp[0][j] = dp[0][j - 1]
        

        for i in range(1, n2):
            for j in range(1, n1):
                if nums2[i] == nums1[j]:
                    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]

复杂度分析

时间复杂度:O($n \times m$) 空间复杂度:O($n \times m$)

Q3. LeetCode 53

本题也可使用贪心算法

前四步:

  1. 使用一维的dp数组,dp[i]表示以nums[i]结尾的连续子数组的最大和
  2. 递推公式:因为是连续的子数组,所以就是两种情况:1. 将其和之前的子数组结合在一起;2. 以当前位置作为子数组的起始位置。$dp[i] = max(dp[i - 1] + nums[i], nums[i])$
  3. 初始化:整体初始化为0,第一个数字只能由自己构成子数组,因此 $dp[0]=nums[0]$
  4. 遍历顺序:从前向后遍历(从1开始)
class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        n = len(nums)
        dp = [0] * n
        dp[0] = nums[0]

        for i in range(1, n):
            dp[i] = max(dp[i - 1] + nums[i], nums[i])
        
        return max(dp)

复杂度分析

时间复杂度:O($n$) 空间复杂度:O($n$)

Q4. LeetCode 392

与Q1类似,但本题是要在一个字符串中匹配另一个字符串,所以只会有原字符串删除元素的情况(初始化和状态转换可以省去一部分)

class Solution:
    def isSubsequence(self, s: str, t: str) -> bool:
        n1 = len(s)
        n2 = len(t)
        if n1 == 0:
            return True
        
        if n2 == 0 and n1 != 0:
            return False

        dp = [[0] * n1 for _ in range(n2)]
        for i in range(n2):
            if s[0] == t[i]:
                dp[i][0] = 1
            else:
                if i > 0:
                    dp[i][0] = dp[i - 1][0]

        for i in range(1, n2):
            for j in range(1, n1):
                if t[i] == s[j]:
                    dp[i][j] = dp[i - 1][j - 1] + 1
                else:
                    dp[i][j] = dp[i - 1][j]
        
        return dp[-1][-1] == n1

复杂度分析

时间复杂度:O($n \times m$) 空间复杂度:O($n \times m$)


本题也可以用双指针法,两个指针分别在两个字符串上

class Solution:
    def isSubsequence(self, s: str, t: str) -> bool:
        n1 = len(s)
        n2 = len(t)

        s_idx = 0
        t_idx = 0

        while t_idx < n2 and s_idx < n1:
            if s[s_idx] == t[t_idx]:
                s_idx += 1
            t_idx += 1
        
        if s_idx == n1:
            return True
        
        return False

复杂度分析

时间复杂度:O($n$) 空间复杂度:O(1)