前端算法面试必刷题系列[24]

438 阅读3分钟

这个系列没啥花头,就是纯 leetcode 题目拆解分析,不求用骚气的一行或者小众取巧解法,而是用清晰的代码和足够简单的思路帮你理清题意。让你在面试中再也不怕算法笔试。

41. 不同路径 (unique-paths)

标签

  • 动态规划

题目

leetcode 传送门

这里不贴题了,leetcode打开就行,题目大意:

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

相关知识

动态规划这篇我们了解到动态规划的基本步骤是下面三步:

  1. 寻找最优子结构(状态表示)
  2. 归纳状态转移方程(状态计算)
  3. 边界初始化

基本步骤

接下来我们看下面具体问题

  1. 状态表示:
  • dp(i, j) 表示从左上角走到 (i, j)路径数量, i,j 范围分别是 [0, m)和 [0, n)
  1. 状态转移方程:
  • 由于我们每一步只能向下向右移动一步,那么其实只有从(i-1, j)或者(i, j-1)这两处走过来,那么转移方程也很简单 dp(i, j) = dp(i-1, j) + dp(i, j-1)
  1. 边界初始化:
  • 那么下面就是定边界,我们知道最上面一排和最左边一排,只有一种可能来的路径,最顶时,来的路只有从左边1条最左边来的路只有从上面1条。所以说,i = 0j = 0时,都是边界条件可以把 dp(0, j)dp(i, 0) 都至为 1 表示到达这个点路径可能条数是 1。 那么右下角坐标就是 (m-1, n-1)
1 1 1 
1 0 0
1 0 0

写法实现

var uniquePaths = function(m, n) {
  // 生成一个 m x n 的二维数组 dp
  let dp = new Array(m).fill(0).map(item => new Array(n).fill(0))
  // 初始化边界,dp(i, 0) 和 dp(0, j) 都至为 1
  for (let i = 0; i < m; i++) {
    dp[i][0] = 1
  }
  for (let j = 0; j < n; j++) {
    dp[0][j] = 1
  }
  for (let i = 1; i < m; i++) {
    for (let j = 1; j < n; j++) {
      dp[i][j] = dp[i - 1][j] + dp[i][j-1]
    }
  }
  // (m-1, n-1)就是右下角
  return dp[m-1][n-1]
};

let m = 3, n = 2
console.log(uniquePaths(m, n))

42. 不同路径 II (unique-paths-ii)

标签

  • 动态规划

题目

leetcode 传送门

这里不贴题了,leetcode打开就行,题目大意:

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

到这里都与上题一模一样,不同就是这次有障碍物

基本步骤

总体思路和上题基本相同,动态规划。不同之处,障碍物的处理方法是 dp[i][j]=0

  1. 特判,注意起点就是障碍直接 return
  2. 还是使用边界条件初始化,注意障碍物的判断就好。第一行,只有左边一条,看上一个位置dp[0][i-1]是否是0 和看当前是否是障碍物obstacleGrid[0][i] === 1 就是障碍物
  3. 循环遍历, 绕开障碍 obstacleGrid[i][j] === 1 这些障碍。

写法实现

var uniquePathsWithObstacles = function(obstacleGrid) {
  // 起点就是障碍物,直接没路径 return 0
  if (obstacleGrid.length === 0 || obstacleGrid[0][0] === 1) {
    return 0
  }
  let [m, n] = [obstacleGrid.length, obstacleGrid[0].length]
  let dp = new Array(m).fill(0).map(item => new Array(n).fill(0))
  // 初始边界,左上角为 1
  dp[0][0] = 1
  for (let i = 1; i < n; i++) {
    // 第一行,只有左边一条,看上一个位置dp[0][i-1]是否是 0 和看当前是否是障碍物
    if (dp[0][i-1] !== 0 && obstacleGrid[0][i] !== 1) {
      dp[0][i] = 1
    }
  }
  for (let j = 1; j < m; j++) {
    // 第一列,只有上面来,看上一个位置 dp[j-1][0] 是否是 0 和看当前是否是障碍物 1
    if (dp[j-1][0] !== 0 && obstacleGrid[j][0] !== 1) {
      dp[j][0] = 1
    }
  }
  // 再循环遍历, 并绕开障碍 obstacleGrid[i][j] !== 1
  for (let i = 1; i < m; i++) {
    for (let j = 1; j < n; j++) {
      if (obstacleGrid[i][j] !== 1) {
        dp[i][j] = dp[i - 1][j] + dp[i][j-1]
      } 
    }
  }
  return dp[m-1][n-1]
};

let obstacleGrid = [[0,0,0],[1,0,0],[0,0,0]]
console.log(uniquePathsWithObstacles(obstacleGrid))

另外向大家着重推荐下这位大哥的文章,非常深入浅出,对前端进阶的同学非常有作用,墙裂推荐!!!核心概念和算法拆解系列

今天就到这儿,想跟我一起刷题的小伙伴可以加我微信哦 搜索我的微信号infinity_9368,可以聊天说地 加我暗号 "天王盖地虎" 下一句的英文,验证消息请发给我 presious tower shock the rever monster,我看到就通过,暗号对不上不加哈,加了之后我会尽我所能帮你,但是注意提问方式,建议先看这篇文章:提问的智慧

参考