路径问题

Unique Paths

https://leetcode.com/problems/unique-paths/

给你一个二维数组,问题是从左上角移动到右下角总共有多少条不同的路径。

这个问题其实很简单,我们只需要从左上角递推到右下角即可。

这里有一个优化点是可以事先初始化首行首列的值为 1,这样在循环中我们就不需要反复判断边界条件。

function uniquePaths(m: number, n: number): number {
  const dp: number[][] = Array.from({ length: m }).map(() => new Array(n))
  
  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]
    }
  }
  
  return dp[m - 1][n - 1]
}
typescript

Unique Paths II

https://leetcode.com/problems/unique-paths-ii/

不同路径的进阶问题,与上一题的不同点就是需要考虑遇到障碍物时,后续的路径都为 0。

所以我们在初始化 dp 数组时,需要事先填充初始值为 0,这样后面就不必考虑赋值问题。

然后同样的方法初始化首行首列,需要判断障碍物是否存在,障碍物后续的元素都不需要再被处理。

function uniquePathsWithObstacles(obstacleGrid: number[][]): number {
  const m = obstacleGrid.length
  const n = obstacleGrid[0].length

  const dp: number[][] = Array.from({ length: m }).map(() =>
    new Array(n).fill(0)
  )

  for (let i = 0; i < m; i++) {
    if (obstacleGrid[i][0] === 1) break
    dp[i][0] = 1
  }
  for (let j = 0; j < n; j++) {
    if (obstacleGrid[0][j] === 1) break
    dp[0][j] = 1
  }

  for (let i = 1; i < m; i++) {
    for (let 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]
}
typescript

初始化首行首列时,判断障碍物的逻辑还可以优化下,就是将判断条件提升到 for 循环本身。

function uniquePathsWithObstacles(obstacleGrid: number[][]): number {
  const m = obstacleGrid.length
  const n = obstacleGrid[0].length

  const dp: number[][] = Array.from({ length: m }).map(() =>
    new Array(n).fill(0)
  )

  // optimize
  for (let i = 0; i < m && obstacleGrid[i][0] === 0; i++) {
    dp[i][0] = 1
  }
  for (let j = 0; j < n && obstacleGrid[0][j] === 0; j++) {
    dp[0][j] = 1
  }

  for (let i = 1; i < m; i++) {
    for (let 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]
}
typescript

Minimum Path Sum

https://leetcode.com/problems/minimum-path-sum/

同样是从左上角移动到右下角,不过这里是求一条总和最小的路径。

首先同样初始化首行首列,初始化时我们需要计算累加值,因为循环是从 1 开始。

然后迭代二维数组,每次求得当前元素与 dp 数组左边和上边取最小值 的和,最后将返回右下角元素即可。

function minPathSum(grid: number[][]): number {
  const m = grid.length
  const n = grid[0].length

  const dp: number[][] = Array.from({ length: m }).map(() => new Array(n))

  dp[0][0] = grid[0][0]

  for (let i = 1; i < m; i++) {
    dp[i][0] = grid[i][0] + dp[i - 1][0]
  }
  for (let j = 1; j < n; j++) {
    dp[0][j] = grid[0][j] + dp[0][j - 1]
  }

  for (let i = 1; i < m; i++) {
    for (let j = 1; j < n; j++) {
      dp[i][j] = grid[i][j] + Math.min(dp[i - 1][j], dp[i][j - 1])
    }
  }

  return dp[m - 1][n - 1]
}
typescript

Triangle

https://leetcode.com/problems/triangle/

求三角形的最小路径和,我们可以从右下角向左上角遍历,这样就不用考虑一些边界问题。

// o(m * m) extra space

function minimumTotal(triangle: number[][]): number {
  const n = triangle.length

  const dp: number[][] = Array.from({ length: n }).map(() => new Array(n))

  for (let i = 0; i < n; i++) {
    dp[n - 1][i] = triangle[n - 1][i]
  }

  for (let i = n - 2; i >= 0; i--) {
    for (let j = 0; j <= i; j++) {
      dp[i][j] = triangle[i][j] + Math.min(dp[i + 1][j], dp[i + 1][j + 1])
    }
  }

  return dp[0][0]
}
typescript

我们还可以将 dp 数组优化至 O(n)。

// o(n) extra space

function minimumTotal(triangle: number[][]): number {
  const n = triangle.length

  const dp: number[] = new Array(n)

  for (let i = 0; i < n; i++) {
    dp[i] = triangle[n - 1][i]
  }

  for (let i = n - 2; i >= 0; i--) {
    for (let j = 0; j <= i; j++) {
      dp[j] = triangle[i][j] + Math.min(dp[j], dp[j + 1])
    }
  }

  return dp[0]
}
typescript

Minimum Falling Path Sum

https://leetcode.com/problems/minimum-falling-path-sum/

这道题目和之前都不一样,我们需要求多条路径的最小和。

所以我们也不能向之前那样初始化首行首列的值,而是需要动态判断边界条件,进行求值。

其次我们还需要比较三个数,即上+中,上+左,上+右,然后将最小值赋值给 dp[i][j]

最后需要比较 dp 数组最后一行数组,取它们的最小值。

function minFallingPathSum(matrix: number[][]): number {
  const m = matrix.length
  const n = matrix[0].length

  const dp: number[][] = Array.from({ length: m }).map(() => new Array(n))

  for (let i = 0; i < n; i++) {
    dp[0][i] = matrix[0][i]
  }

  for (let i = 1; i < m; i++) {
    for (let j = 0; j < n; j++) {
      const curr = matrix[i][j]

      dp[i][j] = dp[i - 1][j] + curr

      if (j - 1 >= 0) dp[i][j] = Math.min(dp[i][j], dp[i - 1][j - 1] + curr)
      if (j + 1 < n) dp[i][j] = Math.min(dp[i][j], dp[i - 1][j + 1] + curr)
    }
  }

  return Math.min(...dp[n - 1])
}
typescript