14 | 动态规划

文章介绍了动态规划和回溯法在解决编程问题中的应用,特别是针对LeetCode中的几道经典题目,如爬楼梯的最小成本、粉刷房子的最小成本、最长公共子序列、最小路径之和、分割等和子集以及买卖股票的最佳时机。通过递归和动态规划数组的方式,展示了如何找到问题的最优解并避免重复计算。
摘要由CSDN通过智能技术生成

知识点

  1. 如果题目要求列举出所有的解,那么很有可能需要用回溯法解决。如果题目是求一个问题的最优解(通常是求最大值或最小值),或者求问题的解的数目(或判断问题是否存在解),那么这个题目有可能适合运用动态规划。
  2. 在采用动态规划时总是用递归的思路分析问题,即把大问题分解成小问题,再把小问题的解合起来形成大问题的解。找出描述大问题的解和小问题的解之间递归关系的状态转移方程是采用动态规划解决问题的关键所在。
  3. 在用代码实现动态规划的算法时,如果采用递归的代码按照从上往下的顺序求解,那么每求出一个小问题的解就缓存下来,这样下次再遇到相同的小问题就不用重复计算。另一个实现动态规划算法的方法是按照从下往上的顺序,从解决最小的问题开始,并把已经解决的小问题的解存储下来(大部分面试题都存储在一维数组或二维数组中),然后把小问题的解组合起来逐步解决大问题。

基础练习

1.爬楼梯最少的成本

Leetcode.749

解题思路

一、递归,状态转移方程 f(n) = min(f(i-2) , f(i-1)) + cost[i],缺点时间复杂度高
二、DP数组记录已经计算过的值,自上而下
三、DP数组记录已经计算过的值,自下而上

python3实现

# 一、递归
class Solution:
    def helper(self, cost:List[int], i:int) -> int:
        if i < 2 :
            return cost[i]
        return min(self.helper(cost,i-1), self.helper(cost,i-2)) + cost[i]

    def minCostClimbingStairs(self, cost: List[int]) -> int:
        length = len(cost)
        return min(self.helper(cost,length-2),self.helper(cost,length-1))

# dp数组记录计算过的值,避免重复计算,自上而下
class Solution:
    def helper(self, cost:List[int], i:int, dp:List[int]) -> None:
        if i < 2 :
            dp[i] = cost[i]

        elif dp[i] == 0:
            self.helper(cost,i-2,dp)
            self.helper(cost,i-1,dp)
            dp[i] = min(dp[i-2],dp[i-1]) + cost[i]
            
    def minCostClimbingStairs(self, cost: List[int]) -> int:
        length = len(cost)
        dp = [0] * length
        if length <= 2:
            dp = cost
        
        self.helper(cost,length - 1, dp)
        return min(dp[length-2],dp[length-1])

# dp数组记录计算过的值,避免重复计算,自下而上,无递归
class Solution:
    def minCostClimbingStairs(self, cost: List[int]) -> int:
        length = len(cost)
        dp = [0] * length
        dp[0] = cost[0]
        dp[1] = cost[1]
        for i in range(2,length):
            dp[i] = min(dp[i-1],dp[i-2]) + cost[i] 
        return min(dp[length-1],dp[length-2])



2.粉刷房子

Leetcode.256

解题思路

用三个数组r,b,g保存红蓝绿三种颜色房子的最小成本,由于相邻房子颜色不能一样,可得状态公式为:r( i ) = min( b( i-1) + g ( i-1) ) + cost[i][0]

python3实现

class Solution:
    def minCost(self, costs: List[List[int]]) -> int:
        length = len(costs)
        r,b,g = [0] * length,[0] * length,[0] * length
        if length == 1:
            return min(costs[0],costs[1],costs[2])
        r[0],b[0],g[0] = costs[0][0],costs[0][1],costs[0][2]
        for i in range(1,length):
            r[i] = min(b[i-1],g[i-1]) + costs[i][0]
            b[i] = min(r[i-1],g[i-1]) + costs[i][1]
            g[i] = min(b[i-1],r[i-1]) + costs[i][2]
        
        return min(r[length-1],b[length-1],g[length-1])
            

3. 最长公共子序列

Leetcode.1143

解题思路

  1. 动态规划的题目不能按照演绎法去一步步地找规律,而是直接找每一个状态值和四周状态值的函数,代码很好写。
  2. 注意考虑特殊和边界情况

python3实现

class Solution:
    def longestCommonSubsequence(self, text1: str, text2: str) -> int:
        length1 = len(text1)
        length2 = len(text2)
        if(length1 < length2):
            return self.longestCommonSubsequence(text2,text1)
        
        dp = [[0] * (length2+1)] * (length1+1)

        for i in range(length1):
            for j in range(length2):
                if text1[i] == text2[j]:
                    dp[i+1][j+1] = dp[i][j] + 1
                else:
                    dp[i+1][j+1] = max(dp[i][j+1],dp[i+1][j])
        
        return dp[length1][length2]

4.最小路径之和

Leetcode.64

解题思路

  1. 最重要的是写出状态公示,用二维DP矩阵来保存状态值,初始化第一行和第一列,再填充矩阵中其他的值。
  2. 注意使用. [[0]*m] * n 初始化二维矩阵时,对某个值进行修改会将整列的值进行修改,原因是浅拷贝,同一列指向同一块内存,应该改为 [[0]*m for _ in range(n)], 或者用numpy模块。

python3实现

class Solution:
    def minPathSum(self, grid: List[List[int]]) -> int:
        m = len(grid)
        n = len(grid[0])
        dp =  [[0]*m for _ in range(n)]
        dp[0][0] = grid[0][0]
        for i in range(1,m):
            dp[0][i] = dp[0][i-1]+grid[0][i]  
        for j in range(1,n):
            dp[j][0] = dp[j-1][0]+grid[j][0]
        for i in range(1,n):
            for j in range(1,m):
                dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]

        return dp[m-1][n-1]

5. 分割等和子集

Leetcode.416

解题思路

  1. 背包问题,从重量不同的物品中选择合适的物品放置于背包中。如果每种物品只有一个,选择放入或不放入称之为0-1背包问题,其他问题也可转化为0-1背包问题。
  2. 这道题可以转化为是否可以从数组中选N个数,使得其合等于数组和的一半。每个数字只能选一次。dp[i][j] 的含义为,对于第i个物品来说,如果选择不放,那么状态值等于dp[i-1][j] 的状态值,如果选择放,那么等于dp[i-1][j-nums[i]] 的值。

python3实现

class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        n = len(nums)
        sum = 0
        for i in range(n):
            sum += nums[i]
        
        if sum % 2 == 1:
            return False
        else:
            sum = sum // 2

        dp = [[None] * (sum+1) for _ in range(n+1)]
        for i in range(n+1):
            dp[i][0] = True
        for i in range(1,sum+1):
            dp[0][i] = False
        
        
        for i in range(1,n+1):
            for j in range(1,sum+1):
                dp[i][j] = dp[i-1][j]
                if dp[i][j] is False and j >= nums[i-1]:
                    dp[i][j] = dp[i-1][j - nums[i-1]] 
        print(dp)
        return dp[n][sum]

6. 买卖股票的最佳时机

leetcode.121

解题思路

  1. min表示今天前买入的最小值
  2. nums[i] - min 表示今天之前最小值买入,今天卖出的获利,即今天卖出的最大获利
  3. 比较每天的最大获利,取最大值即可

python3实现

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        num = len(prices)
        if num <= 1:
            return 0
        
        min_num ,max_profit = prices[0], 0
        for i in range(1,num):
            min_num = min(prices[i],min_num)
            max_profit = max(prices[i]- min_num,max_profit)
        return max_profit
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值