前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >《剑指offer》第29天:m x n 网格的最小路径和

《剑指offer》第29天:m x n 网格的最小路径和

作者头像
程序员小浩
发布2020-09-22 10:20:58
6770
发布2020-09-22 10:20:58
举报
文章被收录于专栏:小浩算法

在上一篇中,我们通过分析,顺利完成了“三角形最小路径和”的动态规划题解。在本节中,我们继续看一道相似题型,以求能完全掌握这种“路径和”的问题。话不多说,先看题目:

01、题目分析

第64题:最小路径和

给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明:每次只能向下或者向右移动一步。

示例:

代码语言:javascript
复制
输入:
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小。

这道题有一定难度哦!如果没有思路请回顾上一篇的学习内容! 不建议直接看题解!

02、题目图解

首先我们分析题目,要找的是 最小路径和, 这是个啥意思呢?假设我们有一个 m * n 的矩形 :[[1,3,1],[1,5,1],[4,2,1]]

那从左上角到右下角的最小路径和,我们可以很容易看出就是 1-3-1-1-1 ,这一条路径,结果等于 7 。

题目明确了,我们继续进行分析。该题与上一道求三角形最小路径和一样,题目明显符合可以从子问题的最优解进行构建,所以我们考虑使用动态规划进行求解。首先,我们定义状态:

dp[i][j] : 表示包含第i行j列元素的最小路径和

同样,因为任何一条到达右下角的路径,都会经过 [0,0] 这个元素。所以我们需要对 dp[0][0] 进行初始化。

dp[0][0] = [0][0]位置所在的元素值

继续分析,根据题目给的条件,如果我们要求 dp[i][j] ,那么它一定是从自己的上方或者左边移动而来。如下图所示:

5,只能从3或者1移动而来2,只能从5或者4移动而来4,从1移动而来3,从1移动而来 (红色位置必须从蓝色位置移动而来)

进而我们得到状态转移方程:

dp[i][j] = min(dp[i-1][j],dp[i][j-1]) + grid[i][j]

同样我们需要考虑两种特殊情况:

  • 最上面一行,只能由左边移动而来(1-3-1)
  • 最左边一列,只能由上面移动而来(1-1-4)

最后,因为我们的目标是从左上角走到右下角整个网格的最小路径和其实就是包含右下角元素的最小路径和。即:

设:dp的长度为l 最终结果就是:dp[l-1][len(dp[l-1])-1]

综上我们就分析完了,我们总共进行了 4 步:

  1. 定义状态
  2. 总结状态转移方程
  3. 分析状态转移方程不能满足的特殊情况。
  4. 得到最终解

03、GO语言示例

根据以上分析,可以得到代码如下:

代码语言:javascript
复制
func minPathSum(grid [][]int) int {
 l := len(grid)
 if l < 1 {
  return 0
 }
 dp := make([][]int, l)
 for i, arr := range grid {
  dp[i] = make([]int, len(arr))
 }
 dp[0][0] = grid[0][0]
 for i := 0; i < l; i++ {
  for j := 0; j < len(grid[i]); j++ {
   if i == 0 && j != 0 {
    dp[i][j] = dp[i][j-1] + grid[i][j]
   } else if j == 0 && i != 0 {
    dp[i][j] = dp[i-1][j] + grid[i][j]
   } else if i !=0 && j != 0 {
    dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]
   }
  }
 }
 return dp[l-1][len(dp[l-1])-1]
}

func min(a, b int) int {
 if a > b {
  return b
 }
 return a
}

运行结果:

同样,运行上面的代码,我们发现使用的内存过大。有没有什么办法可以压缩内存呢?通过观察我们发现,在我们自左上角到右下角计算各个节点的最小路径和的过程中,我们只需要使用到之前已经累积计算完毕的数据,并且不会再次访问之前的元素数据。绘制成图如下:(大家看这个过程像不像扫雷,其实如果大家研究扫雷外挂的话,就会发现在扫雷的核心算法中,就有一处颇为类似这种分析方法,这里就不深究了)

优化后的代码如下:

代码语言:javascript
复制
func minPathSum(grid [][]int) int {
 l := len(grid)
 if l < 1 {
  return 0
 }
 for i := 0; i < l; i++ {
  for j := 0; j < len(grid[i]); j++ {
   if i == 0 && j != 0 {
    grid[i][j] = grid[i][j-1] + grid[i][j]
   } else if j == 0 && i != 0 {
    grid[i][j] = grid[i-1][j] + grid[i][j]
   } else if i !=0 && j != 0 {
    grid[i][j] = min(grid[i-1][j], grid[i][j-1]) + grid[i][j]
   }
  }
 }
 return grid[l-1][len(grid[l-1])-1]
}

func min(a, b int) int {
 if a > b {
  return b
 }
 return a
}

运行结果:

java:

代码语言:javascript
复制
class Solution {
    public int minPathSum(int[][] grid) {
        int l = grid.length;
        if (l < 1) {
            return 0;
        }
        for(int i = 0; i < l; i++)  {
            for(int j = 0; j < grid[i].length; j++)  {
                if (i == 0 && j != 0) {
                    grid[i][j] = grid[i][j-1] + grid[i][j];
                } else if (j == 0 && i != 0) {
                    grid[i][j] = grid[i-1][j] + grid[i][j];
                } else if (i !=0) {
                    grid[i][j] = min(grid[i-1][j], grid[i][j-1]) + grid[i][j];
                }
            }
        }
        return grid[l-1][grid[l-1].length - 1];
    }
    
    
    public int min(int a, int b) {
        return a > b ? b : a;
    }

}

运行结果:

课后思考:路径和类问题和之前的子序列类问题有何区别?

我们欢迎你!

我把我写的所有题解、以及一百张思维导图和一千本开源电子书都放在了公众号中~下方扫码回复【资源】即可获取!同时可回复【招聘】加入 BAT 万人求职群!

随意展示一张导图内容(所有的子节点都可以打开):

今日论点:

面试造航母,工作拧螺丝是真的吗?

一些架构、专家、顾问岗,这种体会没那么强烈,但对于多数开发人员,每天干的工作是增删改查,与面试时的各种问题想较,偏差很大。

由于it行业的高薪资,各种行业都往过来转,公司出于应对异常情况的工作必要、压价、筛选候选人等考虑会提高门槛,当然也不乏一些面试官为了炫技,某些不良公司为了套方案,这样才造成了这种怪像。

大家怎么看呢?评论区留下你的想法吧!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-09-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 小浩算法 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 01、题目分析
  • 02、题目图解
  • 03、GO语言示例
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档