代码随想录算法训练营第39天 | ● 62.不同路径 ● 63. 不同路径II

news/2024/12/2 21:46:34/

文章目录

  • 前言
  • 一、62.不同路径
  • 二、63.不同路径II
  • 总结

前言

动态规划


一、62.不同路径

  • 深搜
  • 动态规划
  • 数论

深搜:

注意题目中说机器人每次只能向下或者向右移动一步,那么其实机器人走过的路径可以抽象为一棵二叉树,而叶子节点就是终点!

如图举例:

62.不同路径

此时问题就可以转化为求二叉树叶子节点的个数,代码如下:

class Solution {
private:int dfs(int i, int j, int m, int n) {if (i > m || j > n) return 0; // 越界了if (i == m && j == n) return 1; // 找到一种方法,相当于找到了叶子节点return dfs(i + 1, j, m, n) + dfs(i, j + 1, m, n);}
public:int uniquePaths(int m, int n) {return dfs(1, 1, m, n);}
};

这棵树的深度其实就是m+n-1(深度按从1开始计算)。

那二叉树的节点个数就是 2^(m + n - 1) - 1。可以理解深搜的算法就是遍历了整个满二叉树(其实没有遍历整个满二叉树,只是近似而已)

所以上面深搜代码的时间复杂度为O(2^(m + n - 1) - 1),可以看出,这是指数级别的时间复杂度,是非常大的。


动态规划:

  1. 定dp数组(dp table)以及下标的含义

dp[i][j] :表示从(0 ,0)出发,到(i, j) 有dp[i][j]条不同的路径。

  1. 确定递推公式

想要求dp[i][j],只能有两个方向来推导出来,即dp[i - 1][j] 和 dp[i][j - 1]。

此时在回顾一下 dp[i - 1][j] 表示啥,是从(0, 0)的位置到(i - 1, j)有几条路径,dp[i][j - 1]同理。

那么很自然,dp[i][j] = dp[i - 1][j] + dp[i][j - 1],因为dp[i][j]只有这两个方向过来。

  1. dp数组的初始化

如何初始化呢,首先dp[i][0]一定都是1,因为从(0, 0)的位置到(i, 0)的路径只有一条,那么dp[0][j]也同理。

所以初始化代码为:

for (int i = 0; i < m; i++) dp[i][0] = 1;
for (int j = 0; j < n; j++) dp[0][j] = 1;
  1. 确定遍历顺序

这里要看一下递推公式dp[i][j] = dp[i - 1][j] + dp[i][j - 1],dp[i][j]都是从其上方和左方推导而来,那么从左到右一层一层遍历就可以了。

这样就可以保证推导dp[i][j]的时候,dp[i - 1][j] 和 dp[i][j - 1]一定是有数值的。

  1. 举例推导dp数组

如图所示:

62.不同路径1

代码:

class Solution {public int uniquePaths(int m, int n) {int dp[][] = new int[m][n];for(int i = 0;i<m;i++){dp[i][0] = 1;}for(int j = 0;j<n;j++){dp[0][j] = 1;}for(int i = 1;i<m;i++){for(int j = 1;j<n;j++){dp[i][j] = dp[i-1][j] + dp[i][j-1];}}return dp[m-1][n-1];}
}


数论:

在这个图中,可以看出一共m,n的话,无论怎么走,走到终点都需要 m + n - 2 步。

62.不同路径

在这m + n - 2 步中,一定有 m - 1 步是要向下走的,不用管什么时候向下走。

那么有几种走法呢? 可以转化为,给你m + n - 2个不同的数,随便取m - 1个数,有几种取法。

那么这就是一个组合问题了。

求组合的时候,要防止两个int相乘溢出! 所以不能把算式的分子都算出来,分母都算出来再做除法。

需要在计算分子的时候,不断除以分母,代码如下:

class Solution {
public:int uniquePaths(int m, int n) {long long numerator = 1; // 分子int denominator = m - 1; // 分母int count = m - 1;int t = m + n - 2;while (count--) {numerator *= (t--);while (denominator != 0 && numerator % denominator == 0) {numerator /= denominator;denominator--;}}return numerator;}
};
  • 时间复杂度:O(m)
  • 空间复杂度:O(1)

计算组合问题的代码还是有难度的,特别是处理溢出的情况!

二、63.不同路径II

动规五部曲:

  1. 确定dp数组(dp table)以及下标的含义

dp[i][j] :表示从(0 ,0)出发,到(i, j) 有dp[i][j]条不同的路径。

  1. 确定递推公式

递推公式和62.不同路径一样,dp[i][j] = dp[i - 1][j] + dp[i][j - 1]。

但这里需要注意一点,因为有了障碍,(i, j)如果就是障碍的话应该就保持初始状态(初始状态为0)。

所以代码为:

if (obstacleGrid[i][j] == 0) { // 当(i, j)没有障碍的时候,再推导dp[i][j]dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
  1. dp数组如何初始化

在62.不同路径

(opens new window)不同路径中我们给出如下的初始化:

vector<vector<int>> dp(m, vector<int>(n, 0)); // 初始值为0
for (int i = 0; i < m; i++) dp[i][0] = 1;
for (int j = 0; j < n; j++) dp[0][j] = 1;

因为从(0, 0)的位置到(i, 0)的路径只有一条,所以dp[i][0]一定为1,dp[0][j]也同理。

但如果(i, 0) 这条边有了障碍之后,障碍之后(包括障碍)都是走不到的位置了,所以障碍之后的dp[i][0]应该还是初始值0。

如图:

63.不同路径II

下标(0, j)的初始化情况同理。

所以本题初始化代码为:

vector<vector<int>> dp(m, vector<int>(n, 0));
for (int i = 0; i < m && obstacleGrid[i][0] == 0; i++) dp[i][0] = 1;
for (int j = 0; j < n && obstacleGrid[0][j] == 0; j++) dp[0][j] = 1;

注意代码里for循环的终止条件,一旦遇到obstacleGrid[i][0] == 1的情况就停止dp[i][0]的赋值1的操作,dp[0][j]同理

  1. 确定遍历顺序

从递归公式dp[i][j] = dp[i - 1][j] + dp[i][j - 1] 中可以看出,一定是从左到右一层一层遍历,这样保证推导dp[i][j]的时候,dp[i - 1][j] 和 dp[i][j - 1]一定是有数值。

代码如下:

for (int i = 1; i < m; i++) {for (int j = 1; j < n; j++) {if (obstacleGrid[i][j] == 1) continue;dp[i][j] = dp[i - 1][j] + dp[i][j - 1];}
}
  1. 举例推导dp数组

拿示例1来举例如题:

63.不同路径II1

对应的dp table 如图:

63.不同路径II2

class Solution {public int uniquePathsWithObstacles(int[][] obstacleGrid) {int m = obstacleGrid.length;int n = obstacleGrid[0].length;int dp[][] = new int[m][n];if(obstacleGrid[0][0] == 1 || obstacleGrid[m-1][n-1] == 1){return 0;}for(int i = 0;i<m && obstacleGrid[i][0] ==0;i++){dp[i][0] = 1;}for(int i = 0;i<n && obstacleGrid[0][i] ==0;i++){dp[0][i] = 1;}for(int i = 1;i<m;i++){for(int j =1;j<n;j++){if(obstacleGrid[i][j] ==0){dp[i][j] = dp[i-1][j] + dp[i][j-1];}else{dp[i][j] = 0;}}}return dp[m-1][n-1];}
}


总结

今天去看《奥本海默》。


http://www.ppmy.cn/news/1082126.html

相关文章

肖sir__linux详解__001

linux详解: 1、ifconfig 查看ip地址 2、6版本&#xff1a;防火墙的命令&#xff1a; service iptables status 查看防火墙状态 service iptables statrt 开启防火墙 service iptables stop 关闭防火墙 service iptables restart 重启防火墙状态 7版本&#xff1a; systemctl s…

分页功能实现

大家好 , 我是苏麟 , 今天聊一聊分页功能 . Page分页构造器是mybatisplus包中的一个分页类 . Page分页 引入依赖 <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.1</ver…

docker进阶作业

一、使用mysql:5.6和 owncloud 镜像&#xff0c;构建一个个人网盘。 安装Docker&#xff1a;确保已在CentOS 7.5上安装了Docker。 拉取MySQL 5.6镜像&#xff1a;使用以下命令从Docker Hub上拉取MySQL 5.6镜像。 docker pull mysql:5.6 运行MySQL容器&#xff1a;使用以下命令…

vue2项目中el-input单独使用max和maxlength不生效问题

vue2项目中el-input单独使用max和maxlength不生效问题 今天在vue2的项目中使用element中的<el-input>组件&#xff0c;因为没有使用form所以max和maxlength属性没有生效&#xff0c;下面是解决办法 <el-input placeholder"请输入" v-model"holeDat…

线性代数的学习和整理18:矩阵的秩的各种定理, 秩和维度(未完成)

目录 1 矩阵的秩 矩阵的秩 2 求秩的方法 矩阵的维度秩 矩阵的维度 向量的模&#xff0c;矩阵的模-没有把&#xff0c;难道是面积&#xff1f; 矩阵的平直概念 5 矩阵的初等变换&#xff08;矩阵等价概念的引出&#xff09; 1 为什么要引入矩阵的“秩” 这个概念&#x…

电视显示技术及价格成本对比(2023年)

版权声明&#xff1a;本文为博主原创文章&#xff0c;遵循 CC 4.0 BY-SA 版权协议&#xff0c;转载请附上原文出处链接和本声明。 本文链接&#xff1a;https://blog.csdn.net/zaibeijixing/article/details/132461068 ———————————————— 截止到2023年&#xff…

kotlin实现猜数游戏

游戏规则 1.程序随机生成一个1到100的数字&#xff0c;作为MagicNumber 2.用户根据提示输入数据&#xff0c;只有三次机会输入数据 代码 代码很简单&#xff0c;使用了let内置函数 fun main() {//生成随机数可以使用java的方法//val magicNumber Random().nextInt(11)val ma…

Linux 查看当前目录大小

分析&回答 1. 查看当前目录下所有目录及子目录大小 du -h - . “.”代表当前目录下。也可以换成一个明确的路径 复制代码 2.查看当前文件目录各个文件夹大小 du -h --max-depth1 复制代码 查看指定目录 du -h --max-depth1 /path 复制代码 -h表示用K、M、G的人性化形…