【编程之路】面试必刷TOP101:动态规划(67-71,Python实现)

2023-11-18

面试必刷TOP101:动态规划(67-71,Python实现)

67.不同路径的数目(一)(小试牛刀

67.1 递归

首先我们在左上角第一个格子的时候,有两种行走方式:如果向右走,相当于后面在一个 ( n − 1 ) ∗ m (n−1)∗m (n1)m 的矩阵中查找从左上角到右下角的不同路径数;而如果向下走,相当于后面在一个 n ∗ ( m − 1 ) n∗(m−1) n(m1) 的矩阵中查找从左上角到右下角不同的路径数。而 ( n − 1 ) ∗ m (n−1)∗m (n1)m 的矩阵与 n ∗ ( m − 1 ) n∗(m−1) n(m1) 的矩阵都是 n ∗ m n∗m nm 矩阵的子问题,因此可以使用递归。

step 1:(终止条件) 当矩阵变长n减少到1的时候,很明显只能往下走,没有别的选择了,只有1条路径;同理m减少到1时也是如此。因此此时返回数量为1。
step 2:(返回值) 对于每一级都将其两个子问题返回的结果相加返回给上一级。
step 3:(本级任务) 每一级都有向下或者向右两种路径选择,分别进入相应分支的子问题。

class Solution:
    def uniquePaths(self , m: int, n: int) -> int:
        if m == 1 or n == 1:
            return 1
        else:
            return self.uniquePaths(m-1,n) + self.uniquePaths(m,n-1)

时间复杂度: O ( m n ) O(mn) O(mn),其中 m、n 分别为矩阵的两边长,递归过程对于每个 m 最多都要经过每一种n。
空间复杂度: O ( m + n ) O(m+n) O(m+n),递归栈的最大深度为矩阵两边从 m、n 都到了 1。

67.2 动态规划

如果我们此时就在右下角的格子,那么能够到达该格子的路径只能是它的上方和它的左方两个格子,因此从左上角到右下角的路径数应该是从左上角到它的左边格子和上边格子的路径数之和,因此可以动态规划。

step 1:用 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示大小为 i ∗ j i∗j ij 的矩阵的路径数量,下标从 1 开始。
step 2:(初始条件) i i i 或者 j j j 为 1 的时候,代表矩阵只有一行或者一列,因此只有一种路径。
step 3:(转移方程) 每个格子的路径数只会来自它左边的格子数和上边的格子数,因此状态转移为 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i ] [ j − 1 ] dp[i][j]=dp[i−1][j]+dp[i][j−1] dp[i][j]=dp[i1][j]+dp[i][j1]

class Solution:
    def uniquePaths(self , m: int, n: int) -> int:
        dp = [[0] * (n+1) for i in range(m+1)]
        for i in range(1,m+1):
            for j in range(1,n+1):
                # 只有1行的时候,只有一种路径
                if i == 1:
                    dp[i][j] = 1
                    continue
                # 只有1列的时候,只有一种路径
                if j == 1:
                    dp[i][j] = 1
                    continue
                # 路径数等于左方格子的路径数加上上方格子的路径数
                dp[i][j] = dp[i-1][j] + dp[i][j-1]
        return dp[m][n]

时间复杂度: O ( m n ) O(mn) O(mn),其中 m、n 分别为矩阵的两边长,两层遍历填充整个 dp 数组。
空间复杂度: O ( m n ) O(mn) O(mn),辅助空间 dp 数组为二维数组。

67.3 组合数学

从矩阵左上角走到矩阵右下角,总共需要往下走 m − 1 m−1 m1 步,往右走 n − 1 n−1 n1 步,不同的走法路径在于往下和往右的组合情况,即在一堆往下和往右的序列中,每种排列的情况,序列一共有 m + n − 2 m+n−2 m+n2 个位置,选择其中 n − 1 n−1 n1 个位置作为往下,即不同的走法有 C m + n − 2 n − 1 = ( m + n − 2 ) ! ( n − 1 ) ! ( m − 1 ) ! C^{n-1}_{m+n-2}=\frac{(m+n-2)!}{(n-1)!(m-1)!} Cm+n2n1=(n1)!(m1)!(m+n2)!种情况。

class Solution:
    def fun(self,n:int):
        sum = 1
        for i in range(1,n+1):
            sum = sum * i
        return sum
    def uniquePaths(self , m: int, n: int) -> int:
        return self.fun(m+n-2) // (self.fun(m-1) * self.fun(n-1))

时间复杂度: O ( n ) O(n) O(n),计算过程需要从 1 遍历到 n。
空间复杂度: O ( 1 ) O(1) O(1),常数级变量,无额外辅助空间。

68.矩阵的最小路径和(小试牛刀)

68.1 动态规划

最朴素的解法莫过于枚举所有的路径,然后求和,找出其中最大值。但是像这种有状态值可以转移的问题,我们可以尝试用动态规划。

step 1:我们可以构造一个与矩阵同样大小的二维辅助数组,其中 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示以 ( i , j ) (i,j) (i,j) 位置为终点的最短路径和,则 d p [ 0 ] [ 0 ] = m a t r i x [ 0 ] [ 0 ] dp[0][0]=matrix[0][0] dp[0][0]=matrix[0][0]
step 2:很容易知道第一行与第一列,只能分别向右或向下,没有第二种选择,因此第一行只能由其左边的累加,第一列只能由其上面的累加。
step 3:边缘状态构造好以后,遍历矩阵,补全矩阵中每个位置的dp数组值:如果当前的位置是 ( i , j ) (i,j) (ij),上一步要么是 ( i − 1 , j ) (i−1,j) (i1,j) 往下,要么就是 ( i , j − 1 ) (i,j−1) (i,j1) 往右,那么取其中较小值与当前位置的值相加就是到当前位置的最小路径和,因此状态转移公式为 d p [ i ] [ j ] = m i n ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) + m a t r i x [ i ] [ j ] dp[i][j]=min(dp[i−1][j],dp[i][j−1])+matrix[i][j] dp[i][j]=min(dp[i1][j],dp[i][j1])+matrix[i][j]
step 4:最后移动到 ( n − 1 , m − 1 ) (n−1,m−1) (n1,m1) 的位置就是到右下角的最短路径和。

class Solution:
    def minPathSum(self , matrix: List[List[int]]) -> int:
        n = len(matrix)
        m = len(matrix[0])
        dp = [[0] * (m+1) for i in range(n+1)]
        dp[0][0] = matrix[0][0]
        # 处理第1列
        for i in range(1,n):
            dp[i][0] = matrix[i][0] + dp[i-1][0]
        # 处理第1行
        for j in range(1,m):
            dp[0][j] = matrix[0][j] + dp[0][j-1]
        # 动态规划
        for i in range(1,n):
            for j in range(1,m):
                dp[i][j] = matrix[i][j] + min(dp[i][j-1],dp[i-1][j])
        return dp[n-1][m-1]

时间复杂度: O ( m n ) O(mn) O(mn),单独遍历矩阵的一行一列,然后遍历整个矩阵。
空间复杂度: O ( m n ) O(mn) O(mn),辅助矩阵 d p dp dp 为二维数组。

69.把数字翻译成字符串 (小试牛刀

69.1 动态规划

对于普通数组1-9,译码方式只有一种,但是对于11-19,21-26,译码方式有可选择的两种方案,因此我们使用动态规划将两种方案累计。

step 1:用辅助数组 d p dp dp 表示前i个数的译码方法有多少种。
step 2:对于一个数,我们可以直接译码它,也可以将其与前面的 1 或者 2 组合起来译码:如果直接译码,则 d p [ i ] = d p [ i − 1 ] dp[i]=dp[i−1] dp[i]=dp[i1];如果组合译码,则 d p [ i ] = d p [ i − 2 ] dp[i]=dp[i−2] dp[i]=dp[i2]
step 3:对于只有一种译码方式的,选上种 d p [ i − 1 ] dp[i−1] dp[i1] 即可,对于满足两种译码方式(10,20不能)则是 d p [ i − 1 ] + d p [ i − 2 ] dp[i−1]+dp[i−2] dp[i1]+dp[i2]
step 4:依次相加,最后的 d p [ l e n g t h ] dp[length] dp[length] 即为所求答案。

class Solution:
    def solve(self , nums: str) -> int:
        # 排除 0
        if nums == '0':
            return 0
        # 排除只有一种可能的 10 和 20
        if nums == '10' or nums == '20':
            return 1
        # 当 0 的前面不是 1 或 2 的时候,无法译码,0 种
        for i in range(1,len(nums)):
            if nums[i] == '0':
                if nums[i-1] != '1' and nums[i-1] != '2':
                    return 0
        dp = [1 for i in range(len(nums)+1)]
        for i in range(2,len(nums)+1):
            # 在 11-19,21-26 之间的情况
            if nums[i-2] == '1' and nums[i-1] != '0' or nums[i-2] == '2' and nums[i-1] > '0' and nums[i-1] < '7':
                dp[i] = dp[i-1] + dp[i-2]
            else:
                dp[i] = dp[i-1]
        return dp[len(nums)]

时间复杂度: O ( n ) O(n) O(n),两次遍历都是单层。
空间复杂度: O ( n ) O(n) O(n),辅助数组 d p dp dp

70.兑换零钱(一)(小试牛刀

本题属于背包问题的一种简化版本,学习完本题的思路帮助你解决相似的背包问题。

70.1 动态规划

step 1:可以用 d p [ i ] dp[i] dp[i] 表示要凑出 i i i 元钱需要最小的货币数。
step 2:一开始都设置为最大值 a i m + 1 aim+1 aim+1,因此货币最小 1 元,即货币数不会超过 a i m aim aim
step 3:初始化 d p [ 0 ] = 0 dp[0]=0 dp[0]=0
step 4:后续遍历 1 元到 a i m aim aim 元,枚举每种面值的货币都可能组成的情况,取每次的最小值即可,转移方程为 d p [ i ] = m i n ( d p [ i ] , d p [ i − a r r [ j ] ] + 1 ) dp[i]=min(dp[i],dp[i−arr[j]]+1) dp[i]=min(dp[i],dp[iarr[j]]+1)
step 5:最后比较 d p [ a i m ] dp[aim] dp[aim] 的值是否超过 a i m aim aim,如果超过说明无解,否则返回即可。

class Solution:
    def minMoney(self , arr: List[int], aim: int) -> int:
        if aim < 1:
            return 0
        # dp[i] 代表凑齐 i 元最少需要多少货币数,初始化为 aim+1
        dp = [(aim+1) for i in range(aim+1)]
        dp[0] = 0
        # 遍历 1 到 aim 元
        for i in range(1,aim+1):
            # 每种面值的货币都要枚举
            for j in range(len(arr)):
                # 面值 arr[j] 不超过要凑的钱 i 才能用
                if arr[j] <= i:
                    dp[i] = min(dp[i], dp[i-arr[j]]+1)
        # 如果最终答案大于 aim 代表无解
        if dp[aim] > aim:
            return -1
        else:
            return dp[aim]

时间复杂度: O ( n ⋅ a i m ) O(n·aim) O(naim),第一层遍历枚举 1 元到 a i m aim aim 元,第二层遍历枚举 n n n 种货币面值。
空间复杂度: O ( a i m ) O(aim) O(aim),辅助数组 d p dp dp 的大小。

71.最长上升子序列(一)(小试牛刀)

71.1 动态规划

要找到最长的递增子序列长度,每当我们找到一个位置,它是继续递增的子序列还是不是,它选择前面哪一处接着才能达到最长的递增子序列,这类有状态转移的问题常用方法是动态规划。
在这里插入图片描述

class Solution:
    def LIS(self , arr: List[int]) -> int:
        if len(arr) == 0:
            return 0
        dp = [1] * len(arr)
        for i in range(1,len(arr)):
            for j in range(i):
                # 可能 j 不是所需要的最大的,因此需要 dp[i] < dp[j] + 1
                if arr[i] > arr[j] and dp[i] < dp[j] + 1:
                    dp[i] = dp[j] + 1
        return max(dp)

时间复杂度: O ( n 2 ) O(n^2) O(n2),其中 n 为数组长度,两层遍历循环。
空间复杂度: O ( n ) O(n) O(n),辅助数组 d p dp dp 的空间。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【编程之路】面试必刷TOP101:动态规划(67-71,Python实现) 的相关文章

随机推荐

  • mysql Specified key was too long; max key length is 767 bytes

    MySQL默认的索引最大长度是767字节 在5 5版本开始后可以通过设置innodb large prefix on来增大索引长度 可达到3072字节 但是只有row format DYNAMIC COMPRESSED的情况下索引的长度才能
  • 系统学Python(一)整数、浮点数、布尔值及类型转换

    整数 int类型数据是整数 正整数或负整数 没有小数 不限制长度 举例1 x 2 print x print type x 用tpye 函数可以查看变量类型 举例2 y 3 print y print type y 自动化QQ交流群 704
  • 常用的几款性能测试软件

    JMeter Apache JMeter是一款免费 开源的性能测试工具 广泛应用于Web应用程序和服务的性能测试 它支持模拟多种不同类型的负载 可以测试应用程序在不同压力下的性能表现 并提供丰富的图表和报告来分析测试结果 优点 免费且开源
  • youtube-dl 中文版帮助文档目录

    选项 常规选项 h help打印此帮助文本并退出 version打印程序版本并退出 U update将此程序更新为最新版本 使 确保您具有足够的权限 如果需要 使用sudo运行 i ignore errors继续出现下载错误 例如 跳过播放
  • JQueryUI如何读取本地文件

    1 设计html界面 这用了两个按钮主要是因为file类型的按钮太丑了 后面跟一个输入框 这里把它隐藏 用下面的按钮激活文件选择对话框
  • Adroid游戏开发实例讲解(四)-电子白板附源码

    Adroid游戏开发实例讲解 四 电子白板附源码 程序之美 电子白板 在很多Android设备中经常会用到 比如说Android电视 触摸屏用上手写笔 轻松在上面写字 比如视频教学Android设备 有很多培训教学机构 都放有Android
  • phpstudy php调试,phpStudy vscode 搭建debug调试的教程详解

    下载地址 Xdebug zend extension D phpstudy pro Extensions php php7 3 4nts ext php xdebug dll xdebug collect params 1 xdebug c
  • 论文:ViT(Transformer 图像分类)

    论文 https arxiv org abs 2010 11929 pytorch代码 https github com lucidrains vit pytorch 不了解Transformer的 建议先看这篇 https blog cs
  • Fiddler实现android手机抓包

    目录 一 fiddler的简介 二 安装fiddler 三 fiddler设置 1 设置HTTPS 2 设置允许远程连接 3 重启fillder 使得配置生效 4 查看端口监听 四 android端设置 1 首先查看电脑的 IP 地址 确保
  • AntDB数据库亮相2023操作系统产业大会,携手合作伙伴共建网信生态

    7月5日 以 麒麟遨天 聚创未来 为题的2023操作系统产业大会在中关村国家自主创新示范区展示交易中心顺利召开 亚信科技作为麒麟软件亲密的合作伙伴受邀参会 AntDB数据库生态负责人在会上进行了 与您携手 共建网信生态 的精彩演讲 与政 产
  • c 十进制数转十六进制

    有3种方式实现 其中两种是使用系统函数 另一种是直接自己编写 使用系统函数实现要加入 include
  • 使用QT纯代码创建(查找)对话框详细步骤与代码

    一 创建项目文件 打开Qt Creator gt 文件 gt 新建文件或项目 gt 选择Qt Widgets Application 为项目起名字 输入类的名字 二 了解每个文件的作用 项目创建完毕之后就会出现以下几个文件 先来分别介绍以下
  • 计算机网络的基本概念

    一 计算机网络的定义 1 计算机网络的定义 计算机网络是互连的 自治的计算机的集合 自治 是指互连的计算机系统彼此独立 不存在主从或者控制与被控制的关系 计算机 计算机设备 互连 是指利用通信链路链接相互独立的计算机系统 2 目前最大的 应
  • form表单数据回填

    前言 我相信很多人在做项目的时候都会碰到数据回填 每当有一个修改页面那么 就逃不掉 可以说修改跟回填是已经牢牢挂钩了 那么这时候有很多小伙伴 就会很头疼了 当我们的页面的form表单需要回填的数据特别特别多的时候 这时候如果还一个一个文本框
  • php+golang grpc客户端和服务端详细案例

    测试环境 win10 centos7 9 php7 4 golang1 17 一 安装 protobuf 1 protoc的源码和各个系统的预编译包 https github com protocolbuffers protobuf rel
  • 怎样写好一篇英文论文

    以前觉得不就是写个论文嘛 东西做好了 写还不好写 最近写了两篇文章也审了几篇文章才发现 写东西真是件技术活 一般人还真搞不定 前两天老师专门开了个批斗大会 指着某某人和某某人的论文给我们讲该怎么写论文 在这里大致总结一下 首先声明这是我和我
  • 访谈:小学学历的程序员自主研发出框架级产品

    提到许松森 也许你并不知道他是谁 在Google中敲入这个名字 能找到的结果也寥寥无几 那么做为我们这一期采访的主角 他究竟是用什么在吸引着我们呢 打开许松森的blog 开篇就是 我的悲惨人生 读在字里行间 对他在逆境中的自我成长很是敬佩
  • java关于数组的函数_Java关于数组操作函数

    数组排序及元素查找 sort 方法对Java数组进行排序 binarySearch 方法来查找数组中的元素 返回该元素所在的位置 import java util public classtest public static voidmai
  • 漏洞挖掘之乱拳打死老师傅——Fuzzer

    背景 Fuzzer是一种通过产生一系列非法的 非预期的或者随机的输入向量给目标程序 从而完成自动化的触发和挖掘目标程序中的安全漏洞的软件测试技术 相比于形式化的软件漏洞测试技术 比如 符号执行技术 Fuzzer往往能够在实际的应用中挖掘更多
  • 【编程之路】面试必刷TOP101:动态规划(67-71,Python实现)

    面试必刷TOP101 动态规划 67 71 Python实现 67 不同路径的数目 一 小试牛刀 67 1 递归 首先我们在左上角第一个格子的时候 有两种行走方式 如果向右走 相当于后面在一个 n 1