经典算法题之(六)—— 二维数组迷宫问题

  • Post author:
  • Post category:其他


1.0 无障碍,右下走,代价相等

m*n的方格,要求最短路径从左上角到右下角,有多少种走法(有多少条最短路径)?

解法一:

动态规划,


因为是最短路径,所以每一步只能往右或者往下走


,那么到第n步(不在第一行或者第一列)只有两种方式:从上往下来的,或者左往右来的。

所以可设二维数组:dp[m][n],dp[i][j]即从入口(a[0][0])走到a[i][j]的最短路径数目,容易推知:


状态方程:

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


边界条件:

dp[0][j] 和 dp[i][0]

代码很容易写出来:

int[][] dp = new int[m][n];   // 自动赋值0
//第一行第一列,边界条件
for(int i = 0; i < n; i++)
    dp[0][i] = 1;
for(int i = 1; i < m; i++)    // dp[0][0]已经赋过值
    dp[i][0] = 1;
for(int i = 1; i < m; i++){
    for(int j = 1; j < n; j++)
        dp[i][j] = dp[i][j-1]+dp[i-1][j];
}
return dp[m-1][n-1]l;
    

解法二:

再看这个图:

其实要最快走到出口,必然要往右走n步,往下走m步,总共要走m+n步。同样地,如果一个走法向走了n步,向下走了m步,那么该走法的路径一定是一条最短路径。

即:往右走n步,往下走m步和最短路径是充分必要条件。

再回到问题,对于路途中的当前步,想往下走(m步没有用完的前提下),还是往右走(n步没有用完的前提下)都是任意的。但是,


如果确定了哪些步往右走,则往下走的步数也确定了


,如下图:

红色(往右走)确定了以后,蓝色自然就确定了。即在m+n步中选好了那些步往右(共n步),剩下的都是往下。

这就是数学中的组合问题(not排列),eg:给m+n个球上色(球标号1~m+n):只能上黑色或者白色,有多少种着色法。

所以问题变成从m+n步中挑出n/m步:
C_{m+n}^{n}
或者
C_{m+n}^{m}

代码完全是计算
C_{m+n}^{n}
或者
C_{m+n}^{m}
的问题:

C_{m+n}^{n} = \frac{A_{m +n}^{n}}{n!} = \frac{\frac{(m+n)!}{m!}}{n!} = \frac{(m+n)!}{m!n!}
(高中数学知识)

PS:
A_{m+n}^{n}
是排列(有顺序),
C_{m+n}^{n}
是组合(无顺序)

上代码:

int count = 1;
for(int i = m+n; i > 0; i--){
    if(i > n)
        count *= i;
    else
        count /= i;
}
return count;

变式:有障碍,右下走,代价相等

m*n的方格,只能往右或者往下走,其中有一些格子不能走,用值-1表示,其余的格子可以走,用值1表示,现在要求最短路径从左上角到右下角,有多少种走法(有多少条最短路径)?

这个只是在边界初始化和迭代计算状态方程时加上判断条件即可,并且障碍物的

看代码:

int[][] dp = new int[m][n];   // 自动赋值0
//第一行第一列,边界条件
for(int i = 0; i < n; i++){
    if(1 == a[0][i])
        dp[0][i] = 1;
     else
        break;   // 后面都是默认的0值(0条路径)
}
for(int i = 0; i < m; i++){
    if(1 == a[i][0])
        dp[i][0] = 1;
     else
        break;   // 后面都是默认的0值(0条路径)
}     
for(int i = 1; i < m; i++){
    for(int j = 1; j < n; j++){
        if(1 == a[i][0])                       // 障碍物的dp不计算,还是默认的0,不影响
            dp[i][j] = dp[i][j-1]+dp[i-1][j];   
    } 
}
return dp[m-1][n-1]l;
    

网上也有其他解析:


一个机器人只能走格点且只能向右或向下走

2.0 无障碍,右下走,代价不等

m*n的方格,要求

只能往右走或者往下走

,从左上角到右下角的最短路径是多少?

题目示例:

leetcode 64. 最小路径和

如图:

解答:

思路:还是使用dp,只是状态方程和边界条件变了:

可设二维数组:dp[m][n],dp[i][j]即从入口(a[0][0])走到a[i][j]的最短路径数目,容易推知:


状态方程:dp[i][j] = min(dp[i][j-1] + dp[i-1][j]) + a[i][j]


边界条件:本题dp[0][j] 和 dp[i][0] 成了前n项和的问题(因为只有一种走法:将第一行/列前面的全部走完)

代码很容易写出来:

int[][] dp = new int[m][n];   // 自动赋值0
//第一行第一列,边界条件
dp[0][0] = a[0][0];           
for(int i = 1; i < n; i++)               // dp[0][0]已赋值
    dp[0][i] = dp[0][i-1]+a[0][i];       // 迭代求和
for(int i = 1; i < m; i++)    
    dp[i][0] = dp[i-1][0]+a[i][0];
for(int i = 1; i < m; i++){
    for(int j = 1; j < n; j++)
        dp[i][j] = Math.min(dp[i][j-1],dp[i-1][j])+a[i][j];  // Math.min有多个重载方法,有int的
}
return dp[m-1][n-1]l;

也可见:


给定一个矩阵m,从左上角开始每次只能向右走或者向下走,最后达到右下角的位置

由于右下走比较简单,可以引入负代价:

变式:

无障碍,右下走,代价不等并可能为负值

题目示例:

leetcode


174. 地下城游戏

策略为

自顶而下的动态规划

参考1.0由无障碍到有障碍的变式,本题也有:

变式:有障碍,右下走,代价不等(大于0)

m*n的方格,只能往右或者往下走,其中有一些格子不能走,用值-1表示,其余的格子可以走,且代价不等(均不等于-1且小于等于10000),求从左上角到右下角的最短路径


是多少


参考1.0的引入障碍后的变式:

① 第一行第一列只要碰到障碍,包括障碍在内的后面格子dp均是0(0条路径)

② 剩余的格子,从dp[1][1]开始,按照从左到右,从上往下的顺序,计算dp[i][j]。只有当前不是障碍时,才使用


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



若是障碍,则不处理(默认值为0)

本题是计算最小路径的大小,不是计算有多少条,则对应到上题,本题策略应该为:

① 第一行第一列采用累加策略(只有这一条路可走)计算dp,只要碰到障碍,包括障碍在内的后面格子dp均是


正无穷

② 剩余的格子,从dp[1][1]开始,按照从左到右,从上往下的顺序,计算dp[i][j]。只有当前不是障碍时,才使用


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



若是障碍,则赋值


正无穷

代码:

int[][] dp = new int[m][n];   // 自动赋值0
//第一行第一列,边界条件
dp[0][0] = a[0][0];           
for(int i = 1; i < n; i++){    
    if(-1 != a[0][i])
        dp[0][i] = dp[0][i-1]+a[0][i];      // 迭代求和
    else{
        for(int j = i; j < n; j++)
            dp[0][i] = Integer.MAX_VALUE;   // Integer.MAX_VALUE来表示正无穷
    }
}
for(int i = 1; i < m; i++){   
    if(-1 != a[i][0])
        dp[i][0] = dp[i-1][0]+a[i][0];
    else{
        for(int j = i; j < m; j++) 
            dp[i][0] = Integer.MAX_VALUE;   
    }
}
for(int i = 1; i < m; i++){
    for(int j = 1; j < n; j++){
        if(-1 != a[i][0])
            dp[i][j] = Math.min(dp[i][j-1],dp[i-1][j])+a[i][j];
        else
            dp[i][j] = Integer.MAX_VALUE;   // Integer.MAX_VALUE来表示正无穷
    }
        
}
return dp[m-1][n-1]l;

3.0 有障碍,任意走,代价相等(大于等于0)


即一般性迷宫问题

m*n的方格,要求从左上角到右下角,可以上下左右走,但不能斜着走,1代表可以走,-1代表障碍物,求最短路径(保证有唯一解)?e.g.

其实就是最基础的图的最短路径问题,而本题是代价相同的图最短路径:

解法很经典:DFS或BFS

DFS做的:


仙岛求药(迷宫寻找最短路径)DFS

BFS做的:


迷宫的最短路径之BFS算法


迷宫问题(求最短路径长度和最短路径)

练习的话可以看Leetcode


1091. 二进制矩阵中的最短路径

还有:


980. 不同路径 III

4.0 有障碍,任意走,代价不等(大于等于0)


即一般性的图最短路径问题

图最短路径问题最“暴力”的解法就是BFS和DFS,只是相对3.0,本题的每步代价不一样,代码改动倒很小:

dp[i][j]更新时的条件由 XXX 改为 XXX。

它的变式很多,一个更为简单的变式就是:

变式:无障碍,任意走,代价不等

即没有障碍的迷宫,其实以图的视角看,就是联通度更高的图而已,方法都是和上面的一样,甚至还简单些,不用判断是不是障碍。

当然,图的最短路径是个大学问,Dijkstra算法,plim算法,Flord算法

如果引入负权边,还有其他算法。(2,0虽然有负权边,但是只能右下走,dp一直取了最小值,没有使用DFS和BFS,故没有问题)

大学问,大学问啊。



版权声明:本文为ProLayman原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。