动态规划总结(持续更新)

1.概念

动态规划不是某一种具体的算法,而是一种算法思想:若要解一个给定问题,我们需要解其不同部分(即子问题),再根据子问题的解以得出原问题的解。

2.与其他相似算法的区别

(来自leetcode)
(1)分治
解决分治问题的时候,思路就是想办法把问题的规模减小,有时候减小一个,有时候减小一半,然后将每个小问题的解以及当前的情况组合起来得出最终的结果。例如归并排序和快速排序,归并排序将要排序的数组平均地分成两半,快速排序将数组随机地分成两半。然后不断地对它们递归地进行处理。
这里存在有最优的子结构,即原数组的排序结果是在子数组排序的结果上组合出来的,但是不存在重复子问题,因为不断地对待排序的数组进行对半分的时候,两半边的数据并不重叠,分别解决左半边和右半边的两个子问题的时候,没有子问题重复出现,这是动态规划和分治的区别。
(2)贪心
①关于最优子结构
贪心:每一步的最优解一定包含上一步的最优解,上一步之前的最优解无需记录
动态规划:全局最优解中一定包含某个局部最优解,但不一定包含上一步的局部最优解,因此需要记录之前的所有的局部最优解
②关于子问题最优解组合成原问题最优解的组合方式
贪心:如果把所有的子问题看成一棵树的话,贪心从根出发,每次向下遍历最优子树即可,这里的最优是贪心意义上的最优。此时不需要知道一个节点的所有子树情况,于是构不成一棵完整的树
动态规划:动态规划需要对每一个子树求最优解,直至下面的每一个叶子的值,最后得到一棵完整的树,在所有子树都得到最优解后,将他们组合成答案
③结果正确性
贪心不能保证求得的最后解是最佳的,复杂度低
动态规划本质是穷举法,可以保证结果是最佳的,复杂度高

3.类别

(1)线性动态规划
线性动态规划的主要特点是状态的推导是按照问题规模 i 从小到大依次推过去的,较大规模的问题的解依赖较小规模的问题的解。这里问题规模为 i 的含义是考虑前 i 个元素 [0…i] 时问题的解。
状态转移:dp[n] = f(dp[n-1], …, dp[0])
①单串问题:
1)最长上升子序列(LC 300)

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        if not nums:
            return 0
        dp=[]    
        for i in range(len(nums)):
            dp.append(1)
            for j in range(i):
                if nums[i]>nums[j]:
                    dp[i]=max(dp[i],dp[j]+1)
        return max(dp)

2)最大子序和(LC 53)

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        tmp_sum = 0
        res = nums[0]
        for num in nums:
            tmp_sum = max(tmp_sum + num, num)
            res = max(res, tmp_sum)
        return res

3)打家劫舍(LC 198)

class Solution:
    def rob(self, nums: List[int]) -> int:
        dp=[]
        if not nums:
            return 0
        if len(nums)==1:
            return nums[0]
        if len(nums)==2:
            return max(nums)
        dp.append(nums[0])
        dp.append(max(nums[0:2]))
        for i in range(2,len(nums)):
            dp.append(max(dp[i-2]+nums[i],dp[i-1]))
        return dp[-1]

②双串问题:

③矩阵问题:
1)最小路径和(LC 64)

class Solution:
    def minPathSum(self, grid: List[List[int]]) -> int:
        if not grid or not grid[0]:
            return 0
        r,c=len(grid),len(grid[0])
        # dp=[[0]*c]*r
        dp= [[0] * c for _ in range(r)]
        dp[0][0]=grid[0][0]
        for i in range(1,r):
            dp[i][0]=dp[i-1][0]+grid[i][0]
        for j in range(1,c):
            dp[0][j]=dp[0][j-1]+grid[0][j]
        for i in range(1,r):
            for j in range(1,c):
                dp[i][j]=min(dp[i-1][j],dp[i][j-1])+grid[i][j] 
        return dp[r-1][c-1]

2)三角形最小路径和

class Solution:
    def minimumTotal(self, triangle: List[List[int]]) -> int:
        dp=triangle
        if not triangle:
            return 0
        if len(triangle)==1:
            return triangle[0][0]
        for i in range(1,len(triangle)):
            dp[i][0]=dp[i-1][0]+triangle[i][0]
            dp[i][-1]=dp[i-1][-1]+triangle[i][-1]
        for i in range(2,len(triangle)):
            for j in range(1,i):
                dp[i][j]=min(dp[i-1][j-1],dp[i-1][j])+triangle[i][j]
        return min(dp[len(triangle)-1])

(2)前缀和

(3)区间动态规划
1)最长回文子串(LC 5)

class Solution:
    def longestPalindrome(self, s: str) -> str:
        n=len(s)
        dp=[[False]*n for _ in range(n)]
        if n<2:
            return s
        begin=0
        max_len=1
        for i in range(n):
            dp[i][i]=True
        for l in range(2,n+1):
            for i in range(n):
                if l+i > n:
                    break
                j=l+i-1
                if s[i]!=s[j]:
                    dp[i][j]=False
                else:
                    if l<3:
                        dp[i][j]=True
                    else:
                        dp[i][j]=dp[i+1][j-1]
                if dp[i][j] and l>max_len:
                    begin=i
                    max_len=l
        return s[begin:begin+max_len]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值