【专题讲解】线性DP

引言

状态转移方法是一个线性的转移,每一行依次求解。

例题:

  1. 三角形最大路径
  2. 最长上升子序列和如何得到该序列
  3. 一个非dp的nlgn算法
  4. 最长子序列
  5. 编辑距离
  6. 多次编辑距离

例题1:三角形最大路径

【专题讲解】线性DP_第1张图片

这道题目思路不难,但是细节很多,需要统一处理存储的三角形和dp的坐标对应的关系。

# 自下往上做更方便
N = int(input())
# 这里我多开了一些dp的空间,这样可以简化初始化的问题
dp = [[0]*(N+1) for _ in range(N+2)]
# 这里注意好坐标对应,从1开始,到N 结束
s = [[0]]
for i in range(N):
    a = list(map(int, input().split()))
    s.append(a)
for i in range(N, 0, -1):
    for j in range(i):
        dp[i][j] = max(dp[i+1][j], dp[i+1][j+1])+s[i][j]
print(dp[1][0])

例题2 最长上升子序列

dp的思路,dp[i]维护了一个数组前 i 个数字情况下,且取当前数字的最大值。

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        ## 时间复杂度是O(n2)
        if not nums:
            return 0
        n = len(nums)    
        dp = [1]*n
        for i in range(n):
            for j in range(i):
                if nums[i]>nums[j]:
                    dp[i] = max(dp[i], dp[j]+1)
        return max(dp)

进一步深化,如何得到最长的序列呢?

维护一个数组f[i],每次记录从哪里转移过来。最后进行还原即可。

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        ## 时间复杂度是O(n2)
        if not nums:
            return 0
        n = len(nums)    
        dp = [1]*n
        f = [0]*n
        for i in range(n):
            for j in range(i):
                if nums[i]>nums[j]:
                    if dp[j]+1>dp[i]:
                        f[i] = j
                    dp[i] = max(dp[i], dp[j]+1)
        max_ = max(dp)
        i = dp.index(max_)
        ans  = []
        while len(ans)<max_:
            ans.append(nums[i])
            i = f[i]
        print(ans)

        return max_

如何更低的复杂度实现呢

另外一个问题就是可以给出一个nlgn的算法,这里写出。利用了二分的思路。
维护一个严格单调递增的序列。

class Solution(object):
    def lengthOfLIS(self, nums):
        ## 如果 cell 中元素都比它小,将它插到最后。否则,用它覆盖掉比它大的元素中最小的那个。
        ## 这里的cell并不是实际上的数字,但是长度是正确的
        if not nums:
            return 0
        n = len(nums)
        cell = [nums[0]]
        for i in nums[1:]:
            if i>cell[-1]:
                cell.append(i)
            else:
                m = len(cell)
                right = m-1
                left = 0
                # 二分查找插入的位置,取替换相关位置上,最小的
                # 二分的目标,找到小于这个数字的最大值,因此选left
                while left<=right:
                    mid = left+(right-left)//2
                    if cell[mid] == i:
                        left = mid
                        break
                    elif cell[mid]>i:
                        right = mid-1
                    elif cell[mid]<i:
                        left = mid+1
                cell[left] = i
        return len(cell)

多次编辑距离

【专题讲解】线性DP_第2张图片

其实也只是对编辑距离的问题进行了应用。代码这里不具体写了,思路是对于每个带估计的字符串,都与匹配字符串数组进行依次计算,看看最短编辑距离是多大,然后与操作次数限制进行比较。时间复杂度是 O ( n m ∗ 100 ) O(nm*100) O(nm100)

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