动态规划面试宝典
卢誉声
Autodesk 首席工程师
9629 人已学习
新⼈⾸单¥59
登录后,你可以任选2讲全文学习
课程目录
已完结/共 23 讲
动态规划面试宝典
15
15
1.0x
00:00/00:00
登录|注册

10|面试即正义第二期:常见的动态规划面试题串烧

你好,我是卢誉声。
在前面的课程中,我们使用动态规划解题模板(套路),解决了多种类型的动态规划算法问题。这其中包括背包问题、子数组问题和子序列问题等,它们绝大多数都属于求最优解(最大值和最小值)类型的问题。
除此之外,我们还需要掌握另外两大类型的动归问题,它们分别是求方案总数以及求可行性(True 或 False)。虽然这两类动归问题的提法不同,但我们仍然可以使用之前总结的动态规划解题模板(套路),只需稍作调整就可以了。
那这样的话,我们今天的课程目标也就非常清晰了,就是把这两类典型的动态规划问题弄明白。现在,就让从最简单的题目开始吧!

简单的路径规划

路径规划问题是十分常见的动态规划面试问题,这类问题通常都是模拟现实中的路径规划。一般来说,它会给你一个指定的图,以及与图相对应的约定条件,然后让你计算出路径的总数或最优路径等。我们一般把这种问题归类到求方案总数这一类别中。
现在,我们来看下最简单的路径规划问题。

算法问题分析

问题:一个机器人位于一个 m * n 网格的左上角 (起始点在下图中标记为“开始” ),机器人每次只能向下或者向右移动一步,现在机器人试图达到网格的右下角(在下图中标记为“结束”)。问总共有多少条不同的路径?
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

动态规划是解决多种类型算法问题的重要方法,包括背包问题、子数组问题和子序列问题等。本文从最简单的路径规划问题入手,介绍了动态规划的基本思想和应用。通过分析重叠子问题、无后效性和最优子结构,确定了可以利用动态规划思想进行求解。文章进一步讨论了带障碍的路径规划问题,考虑了网格中存在障碍物的情况,但问题本质并未改变。接着,介绍了跳跃游戏问题,通过分析动态规划特征,给出了状态转移方程和Java、C++的实现代码。通过以上讲解,读者可以快速了解动态规划的应用和解题思路,以及如何应用动态规划解决路径规划和可行性问题。文章还提到了如何将其他类型的问题转化为求可行性类型的动态规划问题,并鼓励读者尝试解决实际的面试问题,加深对解题模板的利用和理解。最后,文章提出了一个课后思考问题,引发读者思考如何在带障碍的路径规划问题的基础上,求出从起点到终点的最大长度。整体而言,本文通过具体问题的讲解和实例代码的提供,帮助读者快速了解动态规划的应用和解题思路,以及如何应用动态规划解决路径规划和可行性问题。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《动态规划面试宝典》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(9)

  • 最新
  • 精选
  • 宋不肥
    带障碍的可行性那个题目在leetcode上要求打出路径,补一个带路劲的c++代码给有需要的小伙伴 //leetcode:面试题 08.02. 迷路的机器人 class Solution { public: vector<vector<int>> pathWithObstacles(vector<vector<int>>& obstacleGrid) { // 判断边界条件 vector<vector<int>> res; int row = obstacleGrid.size(); if(!row) return res; int col = obstacleGrid[0].size(); if(!col) return res; if(obstacleGrid[0][0] == 1 || obstacleGrid[row -1][col -1] == 1) return res; // 初始化边界条件 int dp[row][col];memset(dp,0,sizeof(dp)); dp[row-1][col-1] = 1; for(int i{row-2} ; i>=0;i--) {//初始化最后一列 if(obstacleGrid[i][col-1] == 1) dp[i][col-1] = 0; else dp[i][col-1] = dp[i+1][col-1]; } for(int i{col-2};i>=0;i--) {//初始化最后一行 if(obstacleGrid[row-1][i] == 1) dp[row-1][i] = 0; else dp[row-1][i] = dp[row-1][i+1]; } //求路径 for(int i{row-2};i>=0;--i) { for(int j{col-2}; j>=0 ;--j) { if(obstacleGrid[i][j] == 1) dp[i][j] = 0; else dp[i][j] = max(dp[i+1][j],dp[i][j+1]); } } if(dp[0][0] == 0) return res; int r{0},c{0}; while(r != row-1 || c != col-1) { res.push_back({r,c}); int down{0};//内部变量不会默认初始化C++,一定要小心 if(r < row-1) down = dp[r+1][c]; int right{0}; if(c < col-1) right = dp[r][c+1]; if(down >= right) r++; else c++; } res.push_back({row-1,col-1}); return res; } }; 话说回来,感觉老师这个专利每篇质量不一啊,大部分都挺好,但背包问题那两讲,图画的有问题,备忘录,重叠子问题部分描述的有些晦涩,劝退了好多小伙伴啊,感觉把那两章重新写一哈,这个专栏的价值能翻一倍

    作者回复: 没有问题,很好,有时间可以多刷刷leetcode,给自己提出更高的要求。也可以参考14|面试即正义第三期:刷题指南,熟能生巧~ 有关于重叠子问题,在13|动态规划算法设计的关键:最优子结构与状态依赖有更深入的讲解。对于图的解释,稍后会作出改进和更新。

    2020-11-03
    3
    4
  • 我是一把火
    跳跃游戏题目,我尝试了一下只用一层for循环,dp[i]保存的是所能达到的最远位置,每次需要先判断一下可达的最远位置是否能够到当前位置。 def canJump(arr): dp = [0 for _ in range(len(arr))] dp[0] = arr[0] for i in range(1, len(arr)): if dp[i-1] < i: return False dp[i] = max(dp[i-1], i + arr[i]) return True

    作者回复: 没有问题,赞! 这道题目动态规划本身也不是最优方案,有很多的可优化之处。

    2020-10-07
    6
    3
  • Jonathan
    带障碍物的不同路径中,初始化操作除了(0,0)位置是根据数组值进行判断,其他应该是根据上一个dp值进行判断吧。比如(0,1)位置是障碍物,那么(0,2)肯定就是0。因为没办法通过(0,0)走到(0,2)

    作者回复: 只要有一个格子具有障碍物后面都是0,因为无法向上走。

    2021-04-12
    2
    2
  • 傻猫周大福
    为何不试试逆序遍历呢 func JumpGame(arr []int) bool { index := len(arr) - 1 for i := index - 1; i >= 0; i-- { if i+arr[i] >= index { index = i } } if index == 0 { return true } return false }

    作者回复: 这个问题其实最好的方法不是递归,只是为了训练如何使用递归求解问题。能思考更好的解决方案是很好的!给你一个赞。

    2020-10-11
    2
  • 每天晒白牙
    有障碍物的路径问题,老师的java版代码应该是有问题的,在leetcode上提交是失败的,在初始化那块有点问题,第一列只要存在障碍物,应该直接break出循环,第一行的初始化也一样 public int uniquePathsWithObstacles(int[][] obstacleGrid) { // 行数 int m = obstacleGrid.length; // 列数 int n = obstacleGrid[0].length; int[][] dp = new int[m][n]; // 初始化状态 for(int i = 0; i < m; i++) { dp[i][0] = obstacleGrid[i][0] == 1 ? 0 : 1; // 第一列只要出现障碍物,下面的肯定是0 if(obstacleGrid[i][0] == 1) { break; } } for(int j = 0; j < n; j++) { dp[0][j] = obstacleGrid[0][j] == 1 ? 0 : 1; // 第一行只要出现障碍物,后面的肯定是0 if(obstacleGrid[0][j] == 1) { break; } } // 状态转移方程 for (int i = 1; i < m; i++) { for (int j = 1; j < n; j++) { if (obstacleGrid[i][j] == 1) { dp[i][j] = 0; } else { dp[i][j] = dp[i-1][j] + dp[i][j-1]; } } } return dp[m-1][n-1]; }

    作者回复: 是的,这里代码需要修改,0代表有障碍,如果第一行或者第一列遇到了障碍,后面的应该都是算会遇到障碍。 感谢你指出问题,正文已修正。

    2021-02-12
    1
  • xuanyuan
    二刷,都是自己独立思考递推公式,找到感觉了,非常赞的专栏

    作者回复: 恩,学习的最好方法就是自己独立思考如何解决问题。

    2020-11-14
    1
  • 帽子狗
    第一个老数学题了, 机器人一定会走 m + n - 2 步,其中有 n - 1步往下走。 所以结果C((m+n-2), (n-1))。 不过计算组合数可能溢出,dp算是大数求组合数的防溢出方案吧。

    作者回复: 其实DP也就是用更加朴素的思想来求解。当然数字一大直接计算组合数的确容易溢出。

    2020-11-09
  • 混混
    带障碍的路径问题中,初始状态,对应第一行或者第一列,出现一个障碍后,后面的路径是不是都应该为0,而不是只有障碍的地方是0?

    作者回复: 如果考虑实现优化的确是这样。但是为了让初始化和后面的求解过程看起来清晰明了,所以这里在初始化就不做过多的延伸了。但是如果理解了动态规划的确可以做这种实现上的优化。

    2020-10-05
    2
  • webmin
    //v[i][j] - i为行,j为列,值为0时表示此路不通,大于0时表示从上方或左方到达此点的长度 // 0 <= i,j < 1000 //0 <= v[i][j] < 1000 int getMaxPathLenWithBlocks(int[][] v) { int m = v.length; int n = v[0].length; int[][] dp = new int[m][n]; // 初始化状态 for (int i = 0; i < m; i ++) { dp[i][0] = v[i][0] == 0 ? 0 : i > 0 ? dp[i-1][0] + v[i][0]: v[i][0]; } for (int j = 0; j < n; j ++) { dp[0][j] = v[0][j] == 0 ? 0 : j > 0 ? dp[0][j-1] + v[0][j]: v[0][j]; } for (int i = 1; i < m; i ++) { // 状态转移过程 for (int j = 1; j < n; j ++) { if (v[i][j] == 0) { dp[i][j] = 0; } else { dp[i][j] = Math.max(dp[i - 1][j],dp[i][j - 1]) + v[i][j]; } } } return dp[m - 1][n - 1]; // 输出答案 }

    作者回复: 没有问题,赞。

    2020-10-05
收起评论
显示
设置
留言
9
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部