Python算法-动态规划(Dynamic Programming)

70-爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。

class Solution:
    def climbStairs(self, n: int) -> int:
        # 动态规划
        if n <= 2:
            return n
        dp = [0]*n
        dp[0] = 1
        dp[1] = 2
        for i in range(2, n):
            dp[i] = dp[i-1] + dp[i-2]
        return dp[-1]
121-买卖股票的最佳时机

输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。

  • 方法1:贪心逐步计算出最小买入价格,以及最大卖出利润
class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        minPrice = float('inf')     # 极大值
        maxProfit = 0               # 初始最大利润为0
        for price in prices:
            minPrice = min(minPrice, price)
            maxProfit = max(maxProfit, price-minPrice)
        return maxProfit
  • 方法2:动态规划

(1)只有卖和不卖两种选择
选择买:min_price = min(min_price, prices[i])
选择卖:dp[i] = prices[i] - min_price
选择不卖:dp[i] = dp[i-1]
动态转移方程:dp[i] = max(dp[i-1], prices[i]-min_price)
(2)初始条件:dp[0] = 0表示

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        if len(prices) == 0:
            return 0
        dp = [0]*len(prices)
        dp[0] = 0
        min_price = prices[0]
        for i in range(1, len(prices)):
            min_price = min(min_price, prices[i])
            dp[i] = max(dp[i-1], prices[i]-min_price)
            print(dp[i])
        return dp[-1]
62-不同路径

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?

class Solution:
    def uniquePaths(self, m: int, n: int) -> int:
        dp = [[0 for i in range(n)] for j in range(m)]
        # 第一种写法
        dp[0][0] = 1
        for i in range(m):
            for j in range(n):
                # 左边有有效值
                if i-1 >= 0 and i-1 < m:
                    dp[i][j] = dp[i][j] + dp[i-1][j]
                # 右边有有效值
                if j-1 >= 0 and j-1 < n:
                    dp[i][j] = dp[i][j] + dp[i][j-1]
        return dp[m-1][n-1]

        # 第二种写法
        for i in range(m):
            dp[i][0] = 1
        for j in range(n):
            dp[0][j] = 1
        
        for i in range(1, m):
            for j in range(1, n):
                dp[i][j] = dp[i-1][j] + dp[i][j-1]
        return dp[m-1][n-1]

        # 动态规划优化
        dp = [0]*n
        for i in range(n):
            dp[i] = 1
        for i in range(1, m):
            dp[0] = 1
            for j in range(1, n):
                dp[j] = dp[j-1] + dp[j]
                
        return dp[n-1]

# 递归写法
# 1 递归关系式:dfs(i,j) = dfs(i-1, j) + dfs(i, j-1)
# 2 找出初始值:如果i或者j有一个为0,那么不能使用关系式
class Solution:
    def uniquePaths(self, m: int, n: int) -> int:
        arr = [[0 for i in range(n)] for j in range(m)]
        return self.dfs(m-1, n-1, arr)
    
    def dfs(self, i, j, arr):
        if i == 0 or j == 0:
            return 1
        if arr[i][j] != 0:
            return arr[i][j]
        arr[i][j] = self.dfs(i-1, j, arr) + self.dfs(i, j-1, arr)
        return arr[i][j]
64-最小路径之和

输入:grid = [[1,3,1],[1,5,1],[4,2,1]]
输出:7
解释:因为路径 1→3→1→1→1 的总和最小。

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

        dp[0][0] = grid[0][0]
        # 初始化最左边的列
        for i in range(1, m):
            dp[i][0] = dp[i-1][0] + grid[i][0]
        # 初始化最上面的行
        for j in range(1, n):
            dp[0][j] = dp[0][j-1] + grid[0][j]

        # 计算剩余的值
        for i in range(1,m):
            for j in range(1,n):
                dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1])
        # print(dp)        
        return dp[m-1][n-1]
72-编辑距离

输入:word1 = "horse", word2 = "ros"
输出:3
解释:
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')

class Solution:
    def minDistance(self, word1: str, word2: str) -> int:
        # 动态规划关系式
        # 插入:dp[i][j] = dp[i][j-1] + 1
        # 删除:dp[i][j] = dp[i-1][j] + 1
        # 替换:dp[i][j] = dp[i-1][j-1] + 1
        # 关系式:dp[i][j] = min(dp[i][j-1], dp[i-1][j], dp[i-1][j-1]) + 1
        m = len(word1)
        n = len(word2)
        dp = [[0 for j in range(n+1)] for i in range(m+1)]
        # dp[0][0...n] 初始化
        for i in range(1,n+1):
            dp[0][i] = dp[0][i-1] + 1
        for j in range(1,m+1):
            dp[j][0] = dp[j-1][0] + 1

        for i in range(1, m+1):
            for j in range(1, n+1):
                if word1[i-1] == word2[j-1]:
                    dp[i][j] = dp[i-1][j-1]
                else:
                    dp[i][j] = min(dp[i][j-1], dp[i-1][j], dp[i-1][j-1]) + 1
        return dp[m][n]
        # 动态规划优化
        m = len(word1)
        n = len(word2)
        dp = [0]*(n+1)

        # 初始化dp[0...n]的值
        for i in range(n+1):
            dp[i] = i
        
        for i in range(1, m+1):
            tmp = dp[0]
            dp[0] = i
            for j in range(1, n+1):
                pre = tmp
                tmp = dp[j]
                if word1[i-1] == word2[j-1]:
                    dp[j] = pre
                else:
                    dp[j] = min(min(dp[j-1], pre), dp[j]) + 1
        return dp[n]
53-最大子序和

输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。

思路(正数实现增加):
若前一个元素大于0,则将其加到当前元素上

操作
变换前 -2 1 -3 4 -1 2 1 -5 4
变换后 -2 1 -2 4 3 5 6 1 5 --> max(nums)

状态转移方程:f(i) = max{f(i-1) + nums[i], nums[i]}

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        n = len(nums)
        for i in range(1, n):
            if nums[i-1] > 0:
                nums[i] += nums[i-1]    # 滚动数组
        return max(nums)
  • 1 定义数组的含义
    dp[i]:以nums[i]结尾的子序的最大和
    max = max(dp[0], dp[1], ... dp[n-1])
  • 2 找到数组元素之间的关系式
    对于每一个房间,小偷可以选择偷或者不偷
    不偷: dp[i] = dp[i-1]
    偷: dp[i] = dp[i-1] + nums[i]
    关系式为:dp[i] = max(dp[i-1], dp[i-1]+nums[i])
  • 3 数组元素的初始值
    dp[0] = nums[0]
class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        if nums == None or len(nums) == 0:
            return 0
        length = len(nums)
        dp = [0]*(length)
        dp[0] = nums[0]
        resMax = dp[0]
        for i in range(1, length):
            dp[i] = max(nums[i], dp[i-1] + nums[i])
            if dp[i] > resMax:
                resMax = dp[i]
        return resMax
198-打家劫舍

输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。

  • 1 定义数组的含义:
    dp[i]当小偷到达i号房屋时,最高可以盗窃的金额是dp[i]
  • 2 数组元素之间的关系:
    小偷只有两种选择,偷还是不偷
    不偷:dp[i] = dp[i-1]
    偷: dp[i] = dp[i-2] + nums[i] # 相邻房间不可以偷窃
    关系式:dp[i] = max(dp[i-1], dp[i-2]+nums[i])
  • 3 找出初始值
    当只有一个房间,必须偷窃:dp[0] = nums[0]
    当有两个房间时,偷窃较多金额的房间:dp[1] = max(nums[0], nums[1])
class Solution:
    def rob(self, nums: List[int]) -> int:
        if len(nums) == 0 or nums == None:
            return 0
        length = len(nums)
        dp = [0]*(length)
        dp[0] = nums[0]
        # 数组边界判定,防止越界
        if len(nums) < 2:
            return nums[0]
        dp[1] = max(nums[0], nums[1])
        for i in range(2, length):
            dp[i] = max(dp[i-1], dp[i-2] + nums[i])
        return dp[length-1]
322-兑换硬币

输入:coins = [1, 2, 5], amount = 11
输出:3 (最少硬币数量)
解释:11 = 5 + 5 + 1

  • 1 定义数组的含义
    dp[i]:凑成总金额n所需的最少硬币数量,dp[amount]就是我们想要的答案

  • 2 找出数组元素之间的关系
    对于每一枚硬币coins[j]我们只有两种选择,选或者不选
    如果选择:dp[i] = dp[i-coins[j]] + 1
    如果不选择:dp[i] = dp[i]
    所以公式为:dp[i] = min(dp[i], dp[i-coins[j]] + 1])

  • 3 找到初始值
    当i=0时,dp[0] = 0
    为了防止取最小值时dp[i]被覆盖,因此给dp[i]赋予一个尽量大的值

  • 动态规划

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        dp = [amount+1]*(amount+1)
        dp[0] = 0

        for i in range(1,amount+1):
            for j in range(len(coins)):
                # 满足兑换的条件
                if coins[j] <= i:
                    dp[i] = min(dp[i], dp[i-coins[j]] + 1)
        if dp[amount] > amount:
            return -1
        else:
            return dp[amount]
120-三角形最小路径和

输入:triangle = [[2],[3,4],[6,5,7],[4,1,8,3]]
输出:11

将上面的数组进行转化:
triangle =
[[2],
[3,4],
[6,5,7],
[4,1,8,3]]
需要注意边界条件!

class Solution:
    def minimumTotal(self, triangle: List[List[int]]) -> int:
        if len(triangle) == 1:
            return triangle[0][0]

        n = len(triangle)
        dp = [[0]*n for _ in range(n)]
        dp[0][0] = triangle[0][0]
        for i in range(1, n):
            # 最左侧的列只能由正上方元素计算
            dp[i][0] = dp[i-1][0] + triangle[i][0]
            for j in range(1, i):
                minNum = min(dp[i-1][j-1], dp[i-1][j])
                dp[i][j] = triangle[i][j] + minNum
            # 最右侧的列只能由左上方元素计算
            dp[i][i] = dp[i-1][i-1] + triangle[i][i]
        print(dp)
        return min(dp[n-1])
10-正则表达式匹配

给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配。

  • '.' 匹配任意单个字符
  • '*' 匹配零个或多个前面的那一个元素
    所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。

输入:s = "ab" p = "."
输出:true
解释:".
" 表示可匹配零个或多个('*')任意字符('.')。

public boolean isMatch1(String s, String p){ 
  if(s == null || p == null) return false; 
  int len_s = s.length(); 
  int len_p = p.length(); 
  //存放状态,默认初始值都是 false。 
  boolean[][]dp = new boolean[len_s+1][len_p+1]; 
  //初始化 
  dp[0][0] = true; 
  for(int j = 1; j <= len_p; j++){ 
    if(p.charAt(j-1) == '*') 
    dp[0][j] = dp[0][j-2]; } 
  for(int i = 1; i <= len_s; i++){ 
    for(int j = 1; j <= len_p; j++){ 
    //如果不为‘*’且匹配 
      if(p.charAt(j-1)=='.'||p.charAt(j-1)==s.charAt(i-1)) 
        dp[i][j] = dp[i-1][j-1]; 
        //如果是 * 
      else if(p.charAt(j-1)=='*'){ 
          //如果p[j]前面的字符p[j-1]与s[i]字符不匹配,则匹配0个 
        if(j!=1&&p.charAt(j-2)!='.'&&p.charAt(j-2)!=s.charAt(i-1)){ 
          dp[i][j] = dp[i][j-2]; }
      else{ 
            //否则有三种情况: //匹配0个,匹配1个,匹配多个 
          dp[i][j] = dp[i][j-2] || dp[i][j-1]||dp[i-1][j]; 
        } 
      } 
    } 
  } 
  return dp[len_s][len_p]; }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,245评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,749评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,960评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,575评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,668评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,670评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,664评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,422评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,864评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,178评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,340评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,015评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,646评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,265评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,494评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,261评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,206评论 2 352

推荐阅读更多精彩内容